热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

devgridview刷新时跳到原位置_解读pkg.go.dev的设计和实现:设计篇

文章较长,建议收藏,抽完整时间阅读。觉得不错帮转发下!北京时间2020年6月15日22点左右,Go官方发博文(https:b

文章较长,建议收藏,抽完整时间阅读。觉得不错帮转发下!

北京时间 2020 年 6 月 15 日 22 点左右,Go 官方发博文(https://blg.golang.org/pkgsite)宣布,pkg.go.dev 开源了。开源代码托管在 Google 自有仓库 https://go.googlesource.com/pkgsite,不过在 GitHub 上提供了镜像:https://github.com/golang/pkgsite。同时,对于该项目的任何 issue,通过 Go 主仓库进行管理,即 https://github.com/golang/go/labels/go.dev。

Go 作者们可能也没有想到,经过这么多年的发展,Go 被使用最广的竟然是 Web 开发,这可能得益于一开始 Go 就对 http 有很好的支持,也因此涌现出大量的 Web 框架,其中知名的有 Gin、Echo、Beego 等。

运营 Go 语言中文网和 Go 社区 8 年有余,发现广大 Gopher 们都苦于没有实战项目可以练手,很多新手学习完 Go 语法后,因为工作中没有用到,不知道该怎么进行项目实战或用 Go 做点什么。这期间也有很多人问我有无适合新手学习的开源项目。也是在去年初,我因此还创建了《Go项目实战》知识星球。现在,Go 官方开源了 pkg.go.dev 这个 Web 项目,爱学习的你,不应该只是看到了这个消息就完事了,应该做点什么,学点什么。

原计划,我是希望通过这个项目,让大家能够很好的学习 Go 是如何进行实际项目开发的。当我深入研究 pkg.go.dev 源码后,我失望了,无论是设计还是实现,水平都很一般。本想着放弃这一系列,但想想还是继续,一方面尝试指出问题,给出认为正确的做法;另一方面,毕竟是官方的项目,开源了相信它会变得更好。

本文包括的内容(并非大纲):

b51be28d3e821172ba6e7df52db05cc4.png

项目架构

先上一张官方的架构图:

08fe6a77e90eeae2a70b244d738774b4.png

architecture

包含了三个核心组件:

  • Frontend:这是一个面向用户的前端组件,处理用户请求,获取数据并展示给用户;
  • Worker:一个后台程序,负责将新模块的信息写入数据库。新模块的数据来自 Go Module Index[1],同时这些模块的内容是从 Go Module Mirror[2] 中下载的;
  • Database:数据库,用于存储站点上提供的所有信息。

图中其他的部分包括:Frontend 组件使用的 Redis 缓存、任务队列和 Scheduler。

Frontend 组件

这是一个简单的 HTTP 服务,它从数据库中获取数据,渲染模板,最后生成 HTML 页面。而搜索功能,是通过 Postgres 的全文搜索实现的,因此没有引入另外的搜索组件,比如:Solr、ElasticSearch。

目前前端做的事情比较简单:从 Postgres 数据库中获取 modules 和 packages 等数据,而 Redis 用于缓存这些数据。

细心的读者会发现这样的设计存在一个问题:更新不及时。前端数据依赖 Worker 组件写入 DB。我临时创建一个 Go 包,用于验证该问题:github.com/polaris1119/testpkg,在 godoc.org 上看到的信息如下:

723300e2f0f7d6790ad4ce5ed33574a5.png

但 pkg.go.dev 上看到的却是:

ae88fe29d530f2fe0defe2156681fde7.png

虽然这个 404 页面告诉你:如果你认为这是一个有效的包路径,可以通过这里的说明[3]尝试获取该包。那这个说明是什么?

因为 pkg.go.dev 的数据是从 proxy.golang.org 下载的。我们会定期监控 Go Module Index[4],以查找要添加到 pkg.go.dev 的新包。如果在 pkg.go.dev 上没有看到你要的包,则可以通过以下任一操作将其添加:

向 proxy.golang.org 请求模块版本,可以请求模块代理协议(Module Proxy Protocol)[5]指定的任何端点,例如:https://proxy.golang.org/github.com/polaris1119/testpkg/@v/list;

通过 go 命令下载相应的包。例如:GOPROXY=https://proxy.golang.org GO111MODULE=on go get github.com/polaris1119/testpkg

可见,这个说明是告诉你怎么将包提交给 proxy.golang.org。即使这样做了,很可能 pkg.go.dev 上还是没有,因为 Frontend 只负责从 DB 获取数据。(几分钟后不出意外应该有了)

c970e53037249f6a08614d0c81dda4dd.png

然而这样的设计大家肯定接受不了,因此出现了几个这样的 issue:#36811[6], #37002[7],#37106[8] 等。为了应对这种情况,所以出现了架构图中的 Frontend Task Queue,用于获取数据库中还不存在的包并将包 master 分支的信息显示给用户,不过截止目前还未实现。不知道到时候以及会如何实现。

这里不得不吐槽了:

  • 这样的问题在设计之初就应该考虑。一个新的系统,不说比之前的 (godoc.org)好,至少不能差。godoc.org 在遇到包没有时会实时获取,而且还支持用户手动刷新;
  • 在访问某个不存在的包时,让用户按照说明操作下,以便 proxy.golang.org 上有这个包。这个过程完全可以程序自动做;不仅如此,还可以考虑自动触发 Worker 进行工作,拉取包数据;

Worker 组件

Worker 的主要工作是下载发现的新模块,进行处理,然后将信息写入数据库以供 Frontend 使用。它提取 README 文件,许可证文件(license)和文档,并将它们写入数据库。它还将与搜索相关的数据写入表(search_documents)。除了直接在模块 zip 中可用的搜索信息外,它还计算每个包的导入者数量。(imports_unique 表保存了每个包导入的其他包)

为了简化处理新模块的工作并利用上限速和重试功能,Worker 使用 Google Cloud Tasks[9]队列来管理要处理的模块列表。当 Worker 程序在索引(index)中找到新模块时,它会将任务添加到队列中。队列以固定的最大速率将任务推送给 Worker。

文档提到:由于 Worker 必须是无状态 HTTP 服务器,因此无法运行后台任务。因此使用Google Cloud Scheduler[10] 来定期执行任务。这些任务通常每分钟运行一次,它们是:

  • 轮询索引(index)以使新模块进入队列;
  • 对于暂时处理失败的模块,重新进入队列;
  • 更新每个包的导入者数量;

从架构图可以看到,Worker 从 index 获取新模块(默认是 index.golang.org),从 proxy 获取 module 的 zip 文件(默认是 proxy.golang.org),最后将信息、数据写入 postgres 数据库。

吐槽:

为什么 Worker 必须是无状态的 HTTP 服务,还无法运行后台任务?这里完全可以通过类似 https://github.com/robfig/cron 这样的库来处理定时任务。竟然设计成启动一个 HTTP 服务,由一个外部定时任务调度器(Google Cloud Scheduler)来调用它提供的接口。

数据库

数据库方面,主要看看表的设计,同时学习下是如何做迁移管理的。

该项目没有使用配置文件,而是通过环境变量来控制。比如 postgres 数据库相关配置信息通过如下环境变量控制:

  • GO_DISCOVERY_DATABASE_USER (default: postgres)
  • GO_DISCOVERY_DATABASE_PASSWORD (default: '')
  • GO_DISCOVERY_DATABASE_HOST (default: localhost)
  • GO_DISCOVERY_DATABASE_NAME (default: discovery-db)

这些配置信息在 internal/config/config.go 文件中。

表的设计是项目很重要的一个环节。为了看到该项目的表设计,安装好 postgres 后,执行如下脚本(类 Unix 系统)创建 discovery-db 数据库:

$ devtools/create_local_db.sh

之后执行迁移操作。

数据库迁移

很多人可能对迁移不了解,这里简单介绍下。

这里说的数据库迁移,主要是指数据库 schema 的变更(当然也包括不同数据源往某个数据库迁移)。我们知道,代码的变更可以通过 Git 进行管理,通过 Git 可以很容易的实现代码的回滚。于是有人就想,代码变更时,很可能数据表的结构也变了,那有没有可能很方便的对数据库的变化进行回滚呢(升降版本)?于是有了 database migrate。就开源项目而言,对数据库自动升降级很有帮助。

然而数据库迁移并没有标准,依赖于具体的工具实现。迁移工具一般分为两种:1)独立的迁移软件,如 Liquibase[11];2)依附于具体语言的库,比如 Go 语言的 migrate[12],不过这个库也可以作为独立的软件使用。

每一次 schema 的变更,有时包括初始化数据,通常会记录在一个单独的脚本文件中。通常每一次数据库变更,应该生成一个对应的文件。我们通过 migrate 这个库具体学习下迁移的操作。

migrate 学习

该项目是 Go 语言实现的数据库迁移工具,支持 CLI 方式使用,也支持作为库导入使用。Migrate 从源读取迁移,并将迁移以正确的顺序应用于数据库。

目前该工具支持如下数据库:

  • PostgreSQL[13]
  • Redshift[14]
  • Ql[15]
  • Cassandra[16]
  • SQLite[17] (todo #165[18])
  • MySQL/ MariaDB[19]
  • Neo4j[20]
  • MongoDB[21]
  • CrateDB[22] (todo #170[23])
  • Shell[24] (todo #171[25])
  • Google Cloud Spanner[26]
  • CockroachDB[27]
  • ClickHouse[28]
  • Firebird[29]
  • MS SQL Server[30]

迁移来源支持如下几种:

  • Filesystem[31] - 从文件系统读取
  • Go-Bindata[32] - 从内嵌的二进制数据读取
  • Github[33] - 从远程 GitHub 仓库读取
  • Github Enterprise[34] - 从远程 Github 企业仓库读取
  • Gitlab[35] - 从远程 Gitlab 仓库读取
  • AWS S3[36] - 从 Amazon Web Services S3 读取
  • Google Cloud Storage[37] - 从 Google Cloud Platform Storage 读取

先安装,以 MacOS 为例:

$ brew install golang-migrate

为了方便演示,以上文创建的 https://github.com/polaris1119/testpkg 为例,clone 下来后,在 testpkg 目录下执行如下命令,创建一个迁移:

$ migrate create -ext sql -dir migrations -seq initial_schema_from_pg_dump

成功后会在 migrations 目录下生成两个文件:

migrations├── 000001_initial_schema_from_pg_dump.down.sql└── 000001_initial_schema_from_pg_dump.up.sql

这里有两个基本概念需要清楚:up 和 down,上面两个文件中有包含。

  • up 表示升级到当前版本;
  • down 表示回退到上一版本;

所以,我们在上面两个文件中填上如下内容:

// up 文件的内容CREATE TABLE gopher (    id serial PRIMARY KEY,    username varchar(31) NOT NULL DEFAULT '',    email varchar(63) NOT NULL DEFAULT '',    created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP);comment on table gopher is 'gopher用户表';comment on column gopher.username is '用户名';// down 文件的内容DROP TABLE    gopher;

  • up 文件的内容是创建表 gopher;
  • down 文件的内容是删除表 gopher;

之后就可以进行迁移操作了(这里假定你本地已经装上 postgres,密码是 123456,同时创建了 testpkg 数据库):

$ migrate -database "postgres://postgres:123456@localhost:5432/testpkg?sslmode=disable" -source file:migrations up

这时会发现数据库中多了两个表:gopher 和 schema_migrations。其中 schema_migrations 的内容如下:

versiondirty1FALSE

如果这时再执行如下命令进行“回滚”:

$ migrate -database "postgres://postgres:123456@localhost:5432/testpkg?sslmode=disable" -source file:migrations down

为了安全,会如下提示:

Are you sure you want to apply all down migrations? [y/N]

选择 y,成功后再看看数据库的变化,发现 gopher 表不见了,schema_migrations 表的内容也清空了,因为上次是版本 1 ,这次回退了。

为了进一步了解细节,我们再创建一个迁移,增加一个表:

$ migrate create -ext sql -dir migrations -seq add_article_table

会生成两个文件,现在有 4 个文件了:

migrations├── 000001_initial_schema_from_pg_dump.down.sql├── 000001_initial_schema_from_pg_dump.up.sql├── 000002_add_article_table.down.sql└── 000002_add_article_table.up.sql

注意到没?文件名前缀自动变为了 00002。同样,我们在新生成的两个文件中填上如下内容:

// up 文件内容CREATE TABLE article (    id serial PRIMARY KEY,    title varchar(127) NOT NULL DEFAULT '',    content text NOT NULL,    created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP);// down 文件内容DROP TABLE    article;

之后执行迁移命令:

$ migrate -database "postgres://postgres:123456@localhost:5432/testpkg?sslmode=disable" -source file:migrations up

输出:

1/u initial_schema_from_pg_dump (126.243958ms)2/u add_article_table (160.978164ms)

查看数据库,发现 gopher 和 article 表都有了,schema_migrations 表中 version 字段值是 2,符合预期。

明白迁移是怎么回事了吗?现在回过头来看看 pkgsite 的迁移和数据表。

##pkgsite 的数据表

pkgsite 提供了几个脚本,方便使用。比如上文提到的创建数据库的脚本。创建迁移和执行迁移的脚本分别是:devtools/create_migration.sh 和 devtools/migrate_db.sh。我们是研究 pkgsite,自然是执行迁移:(默认 migrate_db.sh 认为 postgres 数据库账号的密码是空,因为我本地设置了密码是 123456,因此需要在 migrate_db.sh 中加上密码)

$ devtools/migrate_db.sh up

输出:

1/u initial_schema_from_pg_dump (220.913916ms)2/u add_modules_identity (259.674211ms)3/u add_paths_table (294.30702ms)4/u redo_golang_search_config (334.652007ms)5/u change_b_weight (357.625559ms)6/u add_identity_keys (381.89522ms)7/u add_readme_package_imports_documentation_tables (420.179973ms)8/u remove_golang_text_config (441.527849ms)9/u add_path_tokens_config (462.907176ms)10/u rename_readme_filename_to_file_path (485.666895ms)11/u add_packages_index (509.399738ms)12/u add_modules_series_path_index (318.179716ms)13/u add_version_map_indexes (304.326151ms)14/u add_paths_module_id_index (293.308189ms)15/u add_package_version_states_module_path_version_index (278.168938ms)16/u add_module_version_states_num_packages (277.566312ms)17/u add_module_version_states_num_packages_index (276.640912ms)18/u add_module_version_states_status_index (260.810518ms)19/u add_imports_unique_index (268.141396ms)20/u add_search_documents_module_path_index (272.004077ms)21/u add_version_map_go_mod_path_column (270.42236ms)

一共 21 个版本。这时在 discovery-db 数据库中生成了一系列表。我们拿其中一个的表:packages ,看看它的设计:

CREATE TABLE packages (    path text,    module_path text,    version text,    commit_time timestamp with time zone NOT NULL,    name text NOT NULL,    synopsis text,    license_types text[],    license_paths text[],    v1_path text NOT NULL,    goos text NOT NULL,    goarch text NOT NULL,    redistributable boolean NOT NULL DEFAULT false,    documentation text,    tsv_parent_directories tsvector,    created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,    updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,    CONSTRAINT packages_pkey PRIMARY KEY (path, module_path, version),    CONSTRAINT packages_module_path_version_fkey FOREIGN KEY (module_path, version) REFERENCES modules(module_path, version) ON DELETE CASCADE);COMMENT ON TABLE packages IS 'TABLE packages contains packages in a specific module version.';COMMENT ON COLUMN packages.commit_time IS 'commit_time is the same as verions.commit_time. It is added here so that we can reduce the number of joins in our queries.';COMMENT ON COLUMN packages.tsv_parent_directories IS 'tsv_parent_directories should always be NOT NULL, but it is populated by a trigger, so it will be initially NULL on insert.';

我又要吐槽了:

  • 借用火丁笔记老王的评价:这是应届生设计的吧
  • 表名一般建议使用单数形式;
  • 字符串类型竟然全部是 text,类型选择很随意,没有任何讲究;
  • 主键竟然是三个 text 类型的联合;
  • 。。。

总体看,设计者应该没有经历过大项目,或没有参与过因为量大而遇到性能问题的项目。

设计数据表的建议

这里给出当初在 360 时,公司 DBA 对创建数据表的一些建议或要求:

  • 表名、列名长度不超过 16
  • 每个字段都必须有 NOT NULL DEFAULT '', 如果是 int 则 default 0
  • 主键必须为 int/bigint 类型
  • 如果是 int 类型,且不会存负数,则标记为 UNSIGNED INT
  • 如果 int 类型是 10 以下的几个可枚举值,则使用 TINYINT 类型
  • varchar 长度小于 3000
  • text 字段个数不超过 3 个
  • 每个字段增加 COMMENT 注释,说明字段用途
  • 索引不能有重复
  • 索引个数不能大于 5 个(包括主键)
  • 索引字段必须为 not null,并且有 default 值
  • 请不要使用 MySQL 保留字

以上虽然是针对 MySQL 的,但基本上 Postgres 也是适用的。

代码组织

看看 pkgsite 项目的目录结构:

$ tree -L 2.├── CONTRIBUTING.md├── LICENSE├── PATENTS├── README.md├── all.bash├── cloudbuild.yaml├── cmd│   ├── frontend│   ├── prober│   ├── teeproxy│   └── worker├── content // 存放静态资源(css/img/js 等)│   └── static├── devtools│   ├── compile_js.sh│   ├── create_local_db.sh│   ├── create_migration.sh│   ├── drop_test_dbs.sh│   ├── lib.sh│   └── migrate_db.sh├── doc // 存放设计文档│   ├── architecture.png│   ├── design.md│   ├── frontend.md│   ├── postgres.md│   ├── precommit.md│   └── worker.md├── go.mod├── go.sum├── internal│   ├── auth│   ├── complete│   ├── config│   ├── database│   ├── datasource.go│   ├。。。。。 // 省略了很多├── migrations // 迁移的 sql,前文讲过└── third_party // 用到的第三方前端库    ├── autoComplete.js    └── dialog-polyfill39 directories, 66 files

  • cmd:该目录几乎是 Go 圈约定俗成的,是 Go 官方以及开源界推荐的方式,用于存放 main.main。它包含 4 个子目录,也就是项目可以生成 4 个可执行文件:
  • fronted:对应前文介绍的 Frontend 组件;
  • woker:对应前文介绍的 Worker 组件;
  • prober:探测器,定期探测 frontend,并导出 frontend 的度量指标,以便监控报警和性能追踪;
  • teeproxy:用于处理 godoc.org 上的链接跳转到 pkg.go.dev;
  • internal:除了 cmd 中部分的 Go 代码,其他代码全部在该目录下。目前开源项目习惯包含一个 pkg 包,将一些通用的代码放在该包下。我们知道 internal 包,对其他项目是不可见的。所以,该项目任何包,其他项目都没法直接使用。(上面省略了很多子包)

其他目录说明见上面对应的注释。

关于 main.main 放在哪的问题

上面说,main.main 放在 cmd 目录下几乎是约定俗成的,但针对这个问题还是需要进一步说明一下,毕竟这不是标准或规范。

那关于 main.main,即包含 main 包 和 main 函数的文件(一般是 main.go)放在哪里,目前一般有两种做法:

1)放在项目根目录下。这样放有一个好处,那就是可以方便的通过 go get 进行安装。比如 github.com/polaris1119/golangclub ,按这样的方式安装:

$ go get github.com/polaris1119/golangclub

成功后在 $GOBIN(未设置时取 $GOPATH[0]/bin )目录下会找到 golangclub 可执行文件。但如果你的项目不止一个可执行文件,也就是会存在多个 main.go,这种方式显然没法满足需求。

目前有一些开源项目是这么做的,比如 cobra 生成的框架也是采用的这种方式。

2)创建一个 cmd 目录,专门放置 main.main,有些项目可能会直接将 main.go 放在 cmd 下,但这又回到了上面的方式,而且还没上面的方式方便。一般建议项目存在多个可执行文件时,在 cmd 下创建对应的目录。因为 pkgsite 存在多个可执行文件,因此采用了这种方式。像知名的 Kubernetes 也是采用的这种方式。对于这种方式,通过 go get 可以这样安装:

$ go get -v golang.org/x/pkgsite/cmd/...

这样会将项目所有的可执行文件都生成,你也可以指定生成某一个:

$ go get -v golang.org/x/pkgsite/cmd/frontend

(未完待续)



推荐阅读
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文详细介绍了MySQL表分区的创建、增加和删除方法,包括查看分区数据量和全库数据量的方法。欢迎大家阅读并给予点评。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
  • 本文介绍了一些Java开发项目管理工具及其配置教程,包括团队协同工具worktil,版本管理工具GitLab,自动化构建工具Jenkins,项目管理工具Maven和Maven私服Nexus,以及Mybatis的安装和代码自动生成工具。提供了相关链接供读者参考。 ... [详细]
author-avatar
鬎瀰_418
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有