前言

  在上一章介绍了关于区块链的一些基础知识,这一章会分析指令 geth --datadir dev/data/02 init private-geth/genesis.json 的源码,若你的ethereum的debug环境还没有搭建,那么需要先搭建ethereum的dabug环境

准备工作

  • 创建文件 genesis.json ,内容如下:
{
"config": {
"chainId": 666,  //可用于网络标识,在eip155里有用到,目前来看是做重放保护的,目前eth的公网的网络id为1
"homesteadBlock": 0,  //以太坊版本
"eip155Block": 0,  //(Ethereum Improvement Proposals)简单重访攻击保护,由于是私有链,无硬分叉,此处我们设置为0
"eip158Block": 0  //同上
},
"coinbase" : "0x0000000000000000000000000000000000000000",  //矿工账号
"difficulty" : "0x40000",  //设置当前区块的难度,值越大挖矿难度越大
"extraData" : "",  //附加信息,可以填写任意信息
"gasLimit" : "0xffffffff",  //该值设置对GAS的消耗总量限制,用来限制区块能包含的交易信息总和
"nonce" : "0x0000000000000042",  //是一个64位的随机数,用于挖矿,注意他和mixhash的设置需要满足以太坊的Yellow paper, 4.3.4. Block Header Validity, (44)章节所描述的条件。
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",  //与nonce配合用于挖矿
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",  //上一个区块的hash值,创世区块没有上一个区块,因此设置为0
"timestamp" : "0x00",  //设置创世块的时间戳
"alloc": {
  "1fd4027fe390abaa49e5afde7896ff1e5ecacabf": { "balance": "20000000000000000000" }
 }  //用来预置账号以及账号的以太币数量
}

指令分析

指令: geth --datadir dev/data/02 init private-geth/genesis.json

介绍:上面的指令主要的工作为:

  • 生成创世区块
  • 生成账号的一些信息

分析:

  • dev/data/02 :eth数据保存的地址,主要保存了区块信息和账号信息,日志信息
  • private-geth/genesis.json :eth初始化的一些配置参数

代码分析

接下来就让我们跟以下debug,来一探ethereum的真面目。

  • 进入入口程序

由于我们使用的是 geth 指令,所以我们代开下面的代码:

  • 找到以下函数

main.go:

func main() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

上面的函数是geth命令的入口函数,这段代码首先调用了 app.Run(os.Args) 这个函数, os.Args 为系统参数(例: --datadir dev/data/02 init private-geth/genesis.jso )。那么让我们来看看 app 是什么。

App:

// App is the main structure of a cli application. It is recommended that
// an app be created with the cli.NewApp() function
type App struct {
// The name of the program. Defaults to path.Base(os.Args[0])
Name string
// Full name of command for help, defaults to Name
HelpName string
// Description of the program.
Usage string
// Text to override the USAGE section of help
UsageText string
// Description of the program argument format.
ArgsUsage string
// Version of the program
Version string
// Description of the program
Description string
// List of commands to execute
Commands []Command
// List of flags to parse
Flags []Flag
// Boolean to enable bash completion commands
EnableBashCompletion bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool
// Populate on app startup, only gettable through method Categories()
categories CommandCategories
// An action to execute when the bash-completion flag is set
BashComplete BashCompleteFunc
// An action to execute before any subcommands are run, but after the context is ready
// If a non-nil error is returned, no subcommands are run
Before BeforeFunc
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After AfterFunc // The action to execute when no subcommands are specified
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
// *Note*: support for the deprecated `Action` signature will be removed in a future version
Action interface{} // Execute this function if the proper command cannot be found
CommandNotFound CommandNotFoundFunc
// Execute this function if an usage error occurs
OnUsageError OnUsageErrorFunc
// Compilation date
Compiled time.Time
// List of all authors who contributed
Authors []Author
// Copyright of the binary if any
Copyright string
// Name of Author (Note: Use App.Authors, this is deprecated)
Author string
// Email of Author (Note: Use App.Authors, this is deprecated)
Email string
// Writer writer to write output to
Writer io.Writer
// ErrWriter writes error output
ErrWriter io.Writer
// Other custom info
Metadata map[string]interface{}
// Carries a function which returns app specific info.
ExtraInfo func() map[string]string
// CustomAppHelpTemplate the text template for app help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomAppHelpTemplate string didSetup bool
}

那么 app 是在 main.go 的 init 函数中初始化的,下面让我们来看看 init 函数。

func init() {
// Initialize the CLI app and start Geth
app.Action = geth
app.HideVersion = true // we have a command to print the version
app.Copyright = "Copyright 2013-2018 The go-ethereum Authors"
app.Commands = []cli.Command{
// See chaincmd.go:
initCommand,
importCommand,
exportCommand,
importPreimagesCommand,
exportPreimagesCommand,
copydbCommand,
removedbCommand,
dumpCommand,
// See monitorcmd.go:
monitorCommand,
// See accountcmd.go:
accountCommand,
walletCommand,
// See consolecmd.go:
consoleCommand,
attachCommand,
javascriptCommand,
// See misccmd.go:
makecacheCommand,
makedagCommand,
versionCommand,
bugCommand,
licenseCommand,
// See config.go
dumpConfigCommand,
}
sort.Sort(cli.CommandsByName(app.Commands)) app.Flags = append(app.Flags, nodeFlags...)
app.Flags = append(app.Flags, rpcFlags...)
app.Flags = append(app.Flags, consoleFlags...)
app.Flags = append(app.Flags, debug.Flags...)
app.Flags = append(app.Flags, whisperFlags...) app.Before = func(ctx *cli.Context) error {
runtime.GOMAXPROCS(runtime.NumCPU())
if err := debug.Setup(ctx); err != nil {
return err
}
// Cap the cache allowance and tune the garbage colelctor
var mem gosigar.Mem
if err := mem.Get(); err == nil {
allowance := int(mem.Total / 1024 / 1024 / 3)
if cache := ctx.GlobalInt(utils.CacheFlag.Name); cache > allowance {
log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(allowance))
}
}
// Ensure Go's GC ignores the database cache for trigger percentage
cache := ctx.GlobalInt(utils.CacheFlag.Name)
gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024))) log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc))
godebug.SetGCPercent(int(gogc)) // Start system runtime metrics collection
go metrics.CollectProcessMetrics(3 * time.Second) utils.SetupNetwork(ctx)
return nil
} app.After = func(ctx *cli.Context) error {
debug.Exit()
console.Stdin.Close() // Resets terminal mode.
return nil
}
}

从上面的代码,可以看到,它将所有的指令放到了 app 中缓存了起来。通过这个缓存的指令集,我们可以找到需要执行的代码。下面就让我们来看一下 app.Run(os.Args) 里面的代码。

func (a *App) Run(arguments []string) (err error) {
a.Setup()//在这个里面主要做了三件事:1.初始化App中的commands(主要初始化Command.HelpName)2.增加helpCommand指令到App.Commands,一共21个指令 3.初始化App.categories,主要是给指令分类 // handle the completion flag separately from the flagset since
// completion could be attempted after a flag, but before its value was put
// on the command line. this causes the flagset to interpret the completion
// flag name as the value of the flag before it which is undesirable
// note that we can only do this because the shell autocomplete function
// always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments) // parse flags
set, err := flagSet(a.Name, a.Flags)//这里初始化了一些FlagSet,FlagSet里面存储了一些eth的默认配置,比如networkId=1
if err != nil {
return err
} set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:])//将命令行参数设置到set中,可以通过看里面的代码知道,命令行输入的参数有两种:1.环境配置参数以--为开头 2.命令参数,需要执行代码
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, nil)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
ShowAppHelp(context)
return nerr
}
context.shellComplete = shellComplete if checkCompletions(context) {
return nil
} if err != nil {
if a.OnUsageError != nil {
err := a.OnUsageError(context, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowAppHelp(context)
return err
} if !a.HideHelp && checkHelp(context) {
ShowAppHelp(context)
return nil
} if !a.HideVersion && checkVersion(context) {
ShowVersion(context)
return nil
} if a.After != nil {
defer func() {
if afterErr := a.After(context); afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
} if a.Before != nil {
beforeErr := a.Before(context)
if beforeErr != nil {
ShowAppHelp(context)
HandleExitCoder(beforeErr)
err = beforeErr
return err
}
} args := context.Args()//获取需要执行的命令,当前为init
if args.Present() {
name := args.First()
c := a.Command(name)//查找是否有init命令
if c != nil {
return c.Run(context)//执行init命令
}
} if a.Action == nil {
a.Action = helpCommand.Action
} // Run default Action
err = HandleAction(a.Action, context) HandleExitCoder(err)
return err
}

从上面的注释我们可以知道,命令行输入的参数 init 在 c.Run(context) 这行代码被执行。那么下面就让我们来 c.Run(context) 的代码。

func (c Command) Run(ctx *Context) (err error) {
if len(c.Subcommands) > 0 {
return c.startApp(ctx)
} if !c.HideHelp && (HelpFlag != BoolFlag{}) {
// append help to flags
c.Flags = append(
c.Flags,
HelpFlag,
)
} set, err := flagSet(c.Name, c.Flags)
if err != nil {
return err
}
set.SetOutput(ioutil.Discard) if c.SkipFlagParsing {
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
} else if !c.SkipArgReorder {
firstFlagIndex := -1
terminatorIndex := -1
for index, arg := range ctx.Args() {
if arg == "--" {
terminatorIndex = index
break
} else if arg == "-" {
// Do nothing. A dash alone is not really a flag.
continue
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
firstFlagIndex = index
}
} if firstFlagIndex > -1 {
args := ctx.Args()
regularArgs := make([]string, len(args[1:firstFlagIndex]))
copy(regularArgs, args[1:firstFlagIndex]) var flagArgs []string
if terminatorIndex > -1 {
flagArgs = args[firstFlagIndex:terminatorIndex]
regularArgs = append(regularArgs, args[terminatorIndex:]...)
} else {
flagArgs = args[firstFlagIndex:]
} err = set.Parse(append(flagArgs, regularArgs...))
} else {
err = set.Parse(ctx.Args().Tail())//初始化init命令的参数,该处为private-geth/genesis.json
}
} else {
err = set.Parse(ctx.Args().Tail())
} nerr := normalizeFlags(c.Flags, set)
if nerr != nil {
fmt.Fprintln(ctx.App.Writer, nerr)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return nerr
} context := NewContext(ctx.App, set, ctx)
context.Command = c
if checkCommandCompletions(context, c.Name) {
return nil
} if err != nil {
if c.OnUsageError != nil {
err := c.OnUsageError(context, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
fmt.Fprintln(context.App.Writer)
ShowCommandHelp(context, c.Name)
return err
} if checkCommandHelp(context, c.Name) {
return nil
} if c.After != nil {
defer func() {
afterErr := c.After(context)
if afterErr != nil {
HandleExitCoder(err)
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
} if c.Before != nil {
err = c.Before(context)
if err != nil {
ShowCommandHelp(context, c.Name)
HandleExitCoder(err)
return err
}
} if c.Action == nil {
c.Action = helpSubcommand.Action
} err = HandleAction(c.Action, context)//这一行是用来执行init指令的,指令需要执行的代码链接到了c.Action if err != nil {
HandleExitCoder(err)
}
return err
}

那么最终 init 指令需要执行的代码是 MigrateFlags ,可以在 main.go  initCommand 中看到需要执行的代码。

    initCommand = cli.Command{
Action: utils.MigrateFlags(initGenesis),
Name: "init",
Usage: "Bootstrap and initialize a new genesis block",
ArgsUsage: "<genesisPath>",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.LightModeFlag,
},
Category: "BLOCKCHAIN COMMANDS",
Description: `
The init command initializes a new genesis block and definition for the network.
This is a destructive action and changes the network in which you will be
participating. It expects the genesis file as argument.`,
}

从上面可以看到,执行 MigrateFlags 会先执行 initGenesis ,下面就来看看 initGenesis 的代码。

func initGenesis(ctx *cli.Context) error {
// Make sure we have a valid genesis JSON
genesisPath := ctx.Args().First()//获取命令行参数,此处为private-geth/genesis.json
if len(genesisPath) == 0 {
utils.Fatalf("Must supply path to genesis JSON file")
}
file, err := os.Open(genesisPath)//打开private-geth/genesis.json文件
if err != nil {
utils.Fatalf("Failed to read genesis file: %v", err)
}
defer file.Close() genesis := new(core.Genesis)//构造一个Genesis
if err := json.NewDecoder(file).Decode(genesis); err != nil {//读取配置文件genesis.json,构造genesis结构体
utils.Fatalf("invalid genesis file: %v", err)
}
// Open an initialise both full and light databases
stack := makeFullNode(ctx)//这里面初始化了一些配置信息,网络的一些设置等
for _, name := range []string{"chaindata", "lightchaindata"} {
chaindb, err := stack.OpenDatabase(name, 0, 0)
if err != nil {
utils.Fatalf("Failed to open database: %v", err)
}
_, hash, err := core.SetupGenesisBlock(chaindb, genesis)//这里将创世区块写入leveldb
if err != nil {
utils.Fatalf("Failed to write genesis block: %v", err)
}
log.Info("Successfully wrote genesis state", "database", name, "hash", hash)
}
return nil
}

下面就让我们看看这里是如何构建创世区块的,构建创世区块的过程在 core.SetupGenesisBlock(chaindb, genesis) 里面。

func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
if genesis != nil && genesis.Config == nil {
return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig
} // Just commit the new block if there is no stored genesis block.
stored := rawdb.ReadCanonicalHash(db, 0)
if (stored == common.Hash{}) {
if genesis == nil {
log.Info("Writing default main-net genesis block")
genesis = DefaultGenesisBlock()
} else {
log.Info("Writing custom genesis block")
}
block, err := genesis.Commit(db)//最终我们的代码会走到这里,这里将genesis写入数据库
return genesis.Config, block.Hash(), err
} // Check whether the genesis block is already written.
if genesis != nil {
hash := genesis.ToBlock(nil).Hash()
if hash != stored {
return genesis.Config, hash, &GenesisMismatchError{stored, hash}
}
} // Get the existing chain configuration.
newcfg := genesis.configOrDefault(stored)
storedcfg := rawdb.ReadChainConfig(db, stored)
if storedcfg == nil {
log.Warn("Found genesis block without chain config")
rawdb.WriteChainConfig(db, stored, newcfg)
return newcfg, stored, nil
}
// Special case: don't change the existing config of a non-mainnet chain if no new
// config is supplied. These chains would get AllProtocolChanges (and a compat error)
// if we just continued here.
if genesis == nil && stored != params.MainnetGenesisHash {
return storedcfg, stored, nil
} // Check config compatibility and write the config. Compatibility errors
// are returned to the caller unless we're already at block zero.
height := rawdb.ReadHeaderNumber(db, rawdb.ReadHeadHeaderHash(db))
if height == nil {
return newcfg, stored, fmt.Errorf("missing block number for head header hash")
}
compatErr := storedcfg.CheckCompatible(newcfg, *height)
if compatErr != nil && *height != 0 && compatErr.RewindTo != 0 {
return newcfg, stored, compatErr
}
rawdb.WriteChainConfig(db, stored, newcfg)
return newcfg, stored, nil
}

接下来就让我们跟一下 genesis.Commit(db) 的代码。

// Commit writes the block and state of a genesis specification to the database.
// The block is committed as the canonical head block.
func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) {
block := g.ToBlock(db)//这里面做了两件事:1.写入状态树 2.构造创世区块
if block.Number().Sign() != 0 {
return nil, fmt.Errorf("can't commit genesis block with number > 0")
}
rawdb.WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty)
rawdb.WriteBlock(db, block)
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil)
rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64())
rawdb.WriteHeadBlockHash(db, block.Hash())
rawdb.WriteHeadHeaderHash(db, block.Hash()) config := g.Config
if config == nil {
config = params.AllEthashProtocolChanges
}
rawdb.WriteChainConfig(db, block.Hash(), config)//写入链的配置信息
return block, nil
}

那么让我们来看看 g.ToBlock(db) 的代码。

// ToBlock creates the genesis block and writes state of a genesis specification
// to the given database (or discards it if nil).
func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
if db == nil {
db = ethdb.NewMemDatabase()
}
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
for addr, account := range g.Alloc {
statedb.AddBalance(addr, account.Balance)
statedb.SetCode(addr, account.Code)
statedb.SetNonce(addr, account.Nonce)
for key, value := range account.Storage {
statedb.SetState(addr, key, value)
}
}
root := statedb.IntermediateRoot(false)//这里面构造了一颗状态树,状态树是由一些列的钱包组成
head := &types.Header{//这里构造创世区块头部信息
Number: new(big.Int).SetUint64(g.Number),
Nonce: types.EncodeNonce(g.Nonce),
Time: new(big.Int).SetUint64(g.Timestamp),
ParentHash: g.ParentHash,
Extra: g.ExtraData,
GasLimit: g.GasLimit,
GasUsed: g.GasUsed,
Difficulty: g.Difficulty,
MixDigest: g.Mixhash,
Coinbase: g.Coinbase,
Root: root,
}
if g.GasLimit == 0 {
head.GasLimit = params.GenesisGasLimit
}
if g.Difficulty == nil {
head.Difficulty = params.GenesisDifficulty
}
statedb.Commit(false)
statedb.Database().TrieDB().Commit(root, true) return types.NewBlock(head, nil, nil, nil)//这里构造了一个创世区块
}

下面让我们看下 types.Header 的结构体。

type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`//父区块头的Hash值
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`//当前区块ommers列表的Hash值
Coinbase common.Address `json:"miner" gencodec:"required"`//接收挖此区块费用的矿工钱包地址
Root common.Hash `json:"stateRoot" gencodec:"required"`//状态树根节点的Hash值
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`//包含此区块所列的所有交易的树的根节点Hash值
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`//包含此区块所列的所有交易收据的树的根节点Hash值
Bloom Bloom `json:"logsBloom" gencodec:"required"`//由日志信息组成的一个Bloom过滤器 (数据结构)
Difficulty *big.Int `json:"difficulty" gencodec:"required"`//挖此区块的难度
Number *big.Int `json:"number" gencodec:"required"`//区块编号,也就是区块高度
GasLimit uint64 `json:"gasLimit" gencodec:"required"`//每个区块当前的gasLimit
GasUsed uint64 `json:"gasUsed" gencodec:"required"`//此区块中交易所用的总gas量
Time *big.Int `json:"timestamp" gencodec:"required"`//此区块创建时间戳
Extra []byte `json:"extraData" gencodec:"required"`//与此区块相关的附加数据
MixDigest common.Hash `json:"mixHash" gencodec:"required"`//一个Hash值,当与nonce组合时,证明此区块已经执行了足够的计算
Nonce BlockNonce `json:"nonce" gencodec:"required"`//一个Hash值,当与mixHash组合时,证明此区块已经执行了足够的计算
}

最后我们看下 types.Block 的结构体。

type Block struct {
header *Header//区块头信息
uncles []*Header
transactions Transactions//区块交易信息 // caches
hash atomic.Value
size atomic.Value // Td is used by package core to store the total difficulty
// of the chain up to and including the block.
td *big.Int // These fields are used by package eth to track
// inter-peer block relay.
ReceivedAt time.Time
ReceivedFrom interface{}
}

到这里这一章就介绍结束了。有什么理解错误的地方还望指正。

[ethereum源码分析](3) ethereum初始化指令的更多相关文章

  1. [ethereum源码分析](4) ethereum运行开启console

    前言 在上一章我们介绍了  ethereum初始化指令 ,包括了系统是如何调用指令和指令的执行.在本章节我们将会介绍 geth --datadir dev/data/ --networkid cons ...

  2. [ethereum源码分析](2) ethereum基础知识

    前言 上一章我们介绍了如何搭建ethereum的debug环境.为了更深入的了解ethereum,我们需要了解一些ethereum的相关的知识,本章我们将介绍这些知识. ethereum相关知识 在学 ...

  3. ThinkPHP6源码分析之应用初始化

    ThinkPHP6 源码分析之应用初始化 官方群点击此处. App Construct 先来看看在 __construct 中做了什么,基本任何框架都会在这里做一些基本的操作,也就是从这里开始延伸出去 ...

  4. Spring IOC 容器源码分析 - 余下的初始化工作

    1. 简介 本篇文章是"Spring IOC 容器源码分析"系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的 bea ...

  5. SDL2源码分析1:初始化(SDL_Init())

    ===================================================== SDL源码分析系列文章列表: SDL2源码分析1:初始化(SDL_Init()) SDL2源 ...

  6. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  7. Vue.js 源码分析(二十二) 指令篇 v-model指令详解

    Vue.js提供了v-model指令用于双向数据绑定,比如在输入框上使用时,输入的内容会事实映射到绑定的数据上,绑定的数据又可以显示在页面里,数据显示的过程是自动完成的. v-model本质上不过是语 ...

  8. Bootstrap源码分析系列之初始化和依赖项

    在上一节中我们介绍了Bootstrap整体架构,本节我们将介绍Bootstrap框架第二部分初始化及依赖项,这部分内容位于源码的第8~885行,打开源码这部分内容似乎也不是很难理解.但是请站在一个开发 ...

  9. [ethereum源码分析](1) dubug环境搭建

    前言 因为最近云小哥哥换了一份工作,新公司比较忙,所以一直没有更新新的博客.云小哥哥新的公司是做区块链的,最近在学习区块链相关的东西(也算是乘坐上了区块链这艘大船).本博客是记录我搭建ethereum ...

随机推荐

  1. django学习——通过HttpResponseRedirect 和 reverse实现重定向(转载)

    人分类: django   用django开发web应用, 经常会遇到从一个旧的url转向一个新的url,也就是重定向. HttpResponseRedirect:构造函数的第一个参数是必要的 — 用 ...

  2. Mailx安装与使用

    1.卸载sendmail与postfix yum -y install mailx 2.安装mailx yum -y remove sendmail postfix 3.配置mail.rc vim / ...

  3. mybatis源码级别深度剖析

    mybatis 3.x源码深度解析与最佳实践 Mybatis源码解析优秀博文

  4. [Web 前端] 031 bootstrap 的使用和全局 css 样式

    目录 0. 前言 1. 基本模板 2. 布局容器 2.1 container 2.2 container-fluid 3. 栅格系统 3.1 简介 3.2 栅格参数 3.3 实例:从堆叠到水平排列 2 ...

  5. mysql日志信息查看与设置mysql-bin

    查看 sql查询记录  日志是否开启 SHOW GLOBAL VARIABLES LIKE '%general_log%' 二进制日志 是否开启 SHOW GLOBAL VARIABLES LIKE ...

  6. [2019多校联考(Round 6 T3)]脱单计划 (费用流)

    [2019多校联考(Round 6 T3)]脱单计划 (费用流) 题面 你是一家相亲机构的策划总监,在一次相亲活动中,有 n 个小区的若干男士和 n个小区的若干女士报名了这次活动,你需要将这些参与者两 ...

  7. python中的@property

    @property 可以将python定义的函数“当做”属性访问,从而提供更加友好访问方式,但是有时候setter/getter也是需要的 class People: def __init__(sel ...

  8. 源讯科技(中国)有限公司(Atos Worldline)

    源讯公司是欧洲***的IT服务公司,去年营收达到88亿欧元,在全球52个国家拥有77100名员工.Worldline为Atos(源讯)全资子公司,专注于金融支付领域.Worldline在B2B及B2C ...

  9. L3-002 特殊堆栈 (30 分)

    大家都知道“堆栈”是一种“先进后出”的线性结构,基本操作有“入栈”(将新元素插入栈顶)和“出栈”(将栈顶元素的值返回并从堆栈中将其删除).现请你实现一种特殊的堆栈,它多了一种操作叫“查中值”,即返回堆 ...

  10. 文本检错——中文拼写检查工具FASPell

    最近因为相关项目需要考虑中文文本检错,然后就发现了爱奇艺发布的号称SOTA的FASPell已经开源代码,所以开始着手实现. 检错思想两步:一,掩码语言模型(MLM)产生候选字符:二,CSD过滤候选字符 ...