【毕设扫描器】【参数Fuzz】第二篇:动态爬虫的创建、启动和协程池

本文主要是介绍【毕设扫描器】【参数Fuzz】第二篇:动态爬虫的创建、启动和协程池,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 前言
    • Crawlergo发送请求的代码节点
        • 设置代理调试寻找(未抓到包)
        • WireShark本地抓包(找到代码节点)
    • CrawlerGo设置多线程爬虫
        • 爬虫调用代码
        • 创建爬虫任务
        • 启动爬虫任务
        • 把任务添加到线程池
        • 重要函数表格
    • 接着梳理协程任务的执行
        • 启动 goroutine 执行操作
        • goroutine 的操作:task.Task
        • 配置引擎的库:/pkg/engine
        • 发送请求的 tab 任务
            • 结构体:tab
        • 执行操作函数:tab.Start()
        • 处理响应信息:collect_links.go(分析方向的转折点)
    • 小结

前言

在 《第一篇:数据的定义、读取和装配(爬虫数据和Payload数据)》文章中,我们完成了 URL 数据的读取、以及 Payload 的加载部分。

接下来要做的是发送请求对象,根据响应对象判断是否存在漏洞。

本篇文章要探究的,就是 CrawlerGo 动态爬虫发送请求和处理的过程,以求为参数 Fuzz 发送请求和处理响应提供参考和借鉴。

Crawlergo发送请求的代码节点

CrawlerGo 为动态爬虫装配数据的代码,前面我们基本阅读完毕。接下来看创建爬虫任务、以及启动爬虫部分的代码,查看具体的实现细节。

// crawlergo_cmd.go 代码 311 行// 开始爬虫任务task, err := pkg.NewCrawlerTask(targets, taskConfig)if err != nil {logger.Logger.Error("create crawler task failed.")os.Exit(-1)}……go handleExit(task)logger.Logger.Info("Start crawling.")task.Run()result := task.Result// 追踪 task.Run()
// task_main.go 代码 173行
/*
开始当前任务
*/
func (t *CrawlerTask) Run() {defer t.Pool.Release()  // 释放协程池defer t.Browser.Close() // 关闭浏览器// 为 Targets 设置 reqsFromRobots 变量if t.Config.PathFromRobots {reqsFromRobots := GetPathsFromRobots(*t.Targets[0])logger.Logger.Info("get paths from robots.txt: ", len(reqsFromRobots))t.Targets = append(t.Targets, reqsFromRobots...)}// 为 Targets 设置 reqsByFuzz 变量if t.Config.FuzzDictPath != "" {if t.Config.PathByFuzz {logger.Logger.Warn("`--fuzz-path` is ignored, using `--fuzz-path-dict` instead")}reqsByFuzz := GetPathsByFuzzDict(*t.Targets[0], t.Config.FuzzDictPath)t.Targets = append(t.Targets, reqsByFuzz...)} else if t.Config.PathByFuzz {reqsByFuzz := GetPathsByFuzz(*t.Targets[0])logger.Logger.Info("get paths by fuzzing: ", len(reqsByFuzz))t.Targets = append(t.Targets, reqsByFuzz...)}t.Result.AllReqList = t.Targets[:]// 创建 Request 对象用于 task 的初始化var initTasks []*model.Request// 遍历 Targets 数组for _, req := range t.Targets {if t.smartFilter.DoFilter(req) {logger.Logger.Debugf("filter req: " + req.URL.RequestURI())continue}initTasks = append(initTasks, req)t.Result.ReqList = append(t.Result.ReqList, req)}logger.Logger.Info("filter repeat, target count: ", len(initTasks))for _, req := range initTasks {if !engine2.IsIgnoredByKeywordMatch(*req, t.Config.IgnoreKeywords) {t.addTask2Pool(req)}}t.taskWG.Wait()// 对全部请求进行唯一去重todoFilterAll := make([]*model.Request, len(t.Result.AllReqList))for index := range t.Result.AllReqList {todoFilterAll[index] = t.Result.AllReqList[index]}t.Result.AllReqList = []*model.Request{}var simpleFilter filter2.SimpleFilterfor _, req := range todoFilterAll {if !simpleFilter.UniqueFilter(req) {t.Result.AllReqList = append(t.Result.AllReqList, req)}}// 全部域名t.Result.AllDomainList = AllDomainCollect(t.Result.AllReqList)// 子域名t.Result.SubDomainList = SubDomainCollect(t.Result.AllReqList, t.RootDomain)
}
设置代理调试寻找(未抓到包)

(1)设置代理参数,开启 Burpsuite,没抓到 HTTP 数据包。

--request-proxy http://127.0.0.1:8080 --output-json debug.json http://127.0.0.1/sqli-labs-master/Less-1/?id=1

(2)尝试内置 request-proxy 参数,添加 Value 字段设置参数默认值,从控制台截图可以看到设置成功了,但 Burp 仍然没有收到 HTTP 数据包。

&cli.StringFlag{Name:        "request-proxy",Usage:       "all requests connect through defined proxy server.",Value:       "http://127.0.0.1:8080",Destination: &taskConfig.Proxy,},

在这里插入图片描述

查看 task_main.go 文件,搜索 Proxy,可以看到是在初始化浏览器时加载了代理。

	crawlerTask.Browser = engine2.InitBrowser(taskConf.ChromiumPath, taskConf.IncognitoContext, taskConf.ExtraHeaders, taskConf.Proxy, taskConf.NoHeadless)crawlerTask.RootDomain = targets[0].URL.RootDomain()
WireShark本地抓包(找到代码节点)

参考文章:wireShark抓取本地http包,分析状态。

前往 Npcap官网 下载安装包,安装时记得勾选 Adapter for lookback traffic capture 选项。打开 WireShark 选择该过滤器监听。

调试 CrawlerGo,观察执行哪一部分代码后 HTTP 请求中出现了目标网站的访问记录。

在这里插入图片描述

经过简单对比,发现执行 t.taskWG.Wait() 代码后发送HTTP请求。

// 追踪 task.Run()
// task_main.go 代码 173行
/*
开始当前任务
*/
func (t *CrawlerTask) Run() {defer t.Pool.Release()  // 释放协程池defer t.Browser.Close() // 关闭浏览器// 为 Targets 设置 reqsFromRobots 变量// 为 Targets 设置 reqsByFuzz 变量t.Result.AllReqList = t.Targets[:]// 创建 Request 对象用于 task 的初始化// 遍历 Targets 数组// 遍历初始化任务for _, req := range initTasks {if !engine2.IsIgnoredByKeywordMatch(*req, t.Config.IgnoreKeywords) {t.addTask2Pool(req)}}t.taskWG.Wait()// 对全部请求进行唯一去重todoFilterAll := make([]*model.Request, len(t.Result.AllReqList))for index := range t.Result.AllReqList {todoFilterAll[index] = t.Result.AllReqList[index]}

CrawlerGo设置多线程爬虫

爬虫调用代码
// crawlergo_cmd.go 代码 312 行
// 开始爬虫任务task, err := pkg.NewCrawlerTask(targets, taskConfig)if err != nil {logger.Logger.Error("create crawler task failed.")os.Exit(-1)}……go handleExit(task)logger.Logger.Info("Start crawling.")task.Run()result := task.Result
创建爬虫任务

分析代码,该部分主要操作了如下三部分内容:

  • 给爬虫任务装配数据
  • 以无头模式启动浏览器
  • 创建协程池并设置最大协程数量
	// 开始爬虫任务task, err := pkg.NewCrawlerTask(targets, taskConfig)// task_main.go 代码 70 行
/**新建爬虫任务接收配置好的 Request 、TaskConfig 对象
*/
func NewCrawlerTask(targets []*model.Request, taskConf TaskConfig) (*CrawlerTask, error) {// 定义爬虫任务的数据类型:字典crawlerTask := CrawlerTask{Result: &Result{},Config: &taskConf,smartFilter: filter2.SmartFilter{SimpleFilter: filter2.SimpleFilter{HostLimit: targets[0].URL.Host,},},}// 单个目标,测试也使用单目标站点// 定义 newReq 接收 targets[0],确定通信协议if len(targets) == 1 {_newReq := *targets[0]newReq := &_newReq_newURL := *_newReq.URLnewReq.URL = &_newURL// 首先确定站点使用的通信协议if targets[0].URL.Scheme == "http" {newReq.URL.Scheme = "https"} else {newReq.URL.Scheme = "http"}// 把配置好的 newReq 再添加到 targets,此时 Targets 有两个目标信息的元素targets = append(targets, newReq)}// 把 Targets 添加到 crawlerTask/爬虫任务的字典crawlerTask.Targets = targets[:]// 设置请求对象的 source 为常量 "Target"for _, req := range targets {req.Source = config.FromTarget}// 装配形式参数变量 Taskconfig// 设置 TaskConfig 即 taskConf 的变量,如果未设置则使用默认值设置 taskConf 的 变量.TabRunTimeout.MaxTabsCount.FilterMode.MaxCrawlCount.DomContentLoadedTimeout.EventTriggerInterval.BeforeExitDelay.EventTriggerMode.IgnoreKeywords// 设置请求头变量if taskConf.ExtraHeadersString != "" {err := json.Unmarshal([]byte(taskConf.ExtraHeadersString), &taskConf.ExtraHeaders)if err != nil {logger.Logger.Error("custom headers can't be Unmarshal.")return nil, err}}// 以无头模式开启 chrome.execrawlerTask.Browser = engine2.InitBrowser(taskConf.ChromiumPath, taskConf.IncognitoContext, taskConf.ExtraHeaders, taskConf.Proxy, taskConf.NoHeadless)// 设置爬虫任务的根域名crawlerTask.RootDomain = targets[0].URL.RootDomain()// 初始化智能过滤器crawlerTask.smartFilter.Init()// 创建协程池// 创建协程池,并且把协程池保存到爬虫任务的 Pool 键p, _ := ants.NewPool(taskConf.MaxTabsCount)crawlerTask.Pool = p// 爬虫任务完成数据装配、无头模式启动浏览器、创建协程池return &crawlerTask, nil
}
启动爬虫任务

代码把请求对象添加到线程池,然后等待异步线程执行结束。

task_main.go 代码 210 行的函数 t.addTask2Pool(req),继续追溯。

	// crawlergo_cmd.gotask.Run()/*
开始当前任务
*/
func (t *CrawlerTask) Run() {defer t.Pool.Release()  // 释放协程池defer t.Browser.Close() // 关闭浏览器是否从 robots.txt 获取信息,如果是则把信息添加到 t.Targets 对象是否 Fuzz 目录,如果是则读取字典文件并添加到 t.Targets 对象// 创建数组对象 AllReqListt.Result.AllReqList = t.Targets[:]// 创建 Request 类型的初始化任务变量var initTasks []*model.Requestfor _, req := range t.Targets {if t.smartFilter.DoFilter(req) {logger.Logger.Debugf("filter req: " + req.URL.RequestURI())continue}// 从 t.Targets 对象中获取请求对象initTasks = append(initTasks, req)t.Result.ReqList = append(t.Result.ReqList, req)}logger.Logger.Info("filter repeat, target count: ", len(initTasks))// 遍历初始化任务数组的请求对象,逐个添加到协程池for _, req := range initTasks {if !engine2.IsIgnoredByKeywordMatch(*req, t.Config.IgnoreKeywords) {t.addTask2Pool(req)}}// 运行则发送请求// 等待异步线程执行完成t.taskWG.Wait()// 对全部请求进行唯一去重todoFilterAll := make([]*model.Request, len(t.Result.AllReqList))for index := range t.Result.AllReqList {todoFilterAll[index] = t.Result.AllReqList[index]}t.Result.AllReqList = []*model.Request{}var simpleFilter filter2.SimpleFilterfor _, req := range todoFilterAll {if !simpleFilter.UniqueFilter(req) {t.Result.AllReqList = append(t.Result.AllReqList, req)}}// 全部域名t.Result.AllDomainList = AllDomainCollect(t.Result.AllReqList)// 子域名t.Result.SubDomainList = SubDomainCollect(t.Result.AllReqList, t.RootDomain)
}
把任务添加到线程池
// task_main.go,t.addTask2Pool()位于 210 行
/*开始当前任务*/
func (t *CrawlerTask) Run() {……// 遍历初始化任务数组的请求对象,逐个添加到协程池for _, req := range initTasks {if !engine2.IsIgnoredByKeywordMatch(*req, t.Config.IgnoreKeywords) {t.addTask2Pool(req)}}// 追溯 t.addTask2Pool(req),同文件代码 240 行
/**
添加任务到协程池
添加之前实时过滤
*/
func (t *CrawlerTask) addTask2Pool(req *model.Request) {// 协程相关代码t.taskCountLock.Lock()if t.crawledCount >= t.Config.MaxCrawlCount {t.taskCountLock.Unlock()return} else {t.crawledCount += 1}t.taskCountLock.Unlock()t.taskWG.Add(1)// 传入请求对象(字典类型),追溯 t.generateTabTask(req) ,发现根据请求列表生成tabTask协程任务列表/* 实际上又嵌套了一层字典,字典结构如下所示task := tabTask{crawlerTask: t,browser:     t.Browser,req:         req,}*/task := t.generateTabTask(req)// 测试时F7单步调试,发现跳过了该部分代码// 查询资料发现,该部分代码作用是:以并发的方式调用匿名函数funcgo func() {// 提交协程任务执行err := t.Pool.Submit(task.Task)if err != nil {t.taskWG.Done()logger.Logger.Error("addTask2Pool ", err)}}()
}// 追溯关键代码 t.Pool.submit(task.Task)在 t.Pool.submit(task.Task) 后添加打印提示信息的代码,发现打印了提示信息在该处下断点,进行单步调试发现使用F7或F8进行单步调试时,不会在该段代码的断点停留使用"运行到光标处",才会在该段代码的断点停留,然后就会执行 Run() 里面的代码:t.taskWG.Wait(),此时请求已经发送单步调试跟踪 t.taskWG.Wait(),发现的函数调用如下:task.Run()								crawlergo_cmd.go->t.taskWG.Wait()						task_main.go-> Wait()							src/sync/waitgroup.go->runtime_Semacquire(semap)			src/sync/waitgroup.go->sync_runtime_Semacquire()		src/runtime/sema.goF7步入执行该代码,此时会运行t.Pool.submit()并发送请求。也就是说,执行 t.taskWG.Wait()函数过程中,会执行 t.Pool.submit(),然后会执行协程任务。插个眼:注意func (t *tabTask) Task()
重要函数表格

创建爬虫任务

所属文件函数说明
crawlergo_cmd.gotask, err := pkg.NewCrawlerTask(targets, taskConfig)创建爬虫任务
task_main.goengine2.InitBrowser(xx)以无头模式启动浏览器 chrome.exe
-ants.NewPool(taskConf.MaxTabsCount)创建协程池

接着梳理协程任务的执行

关键代码:task_main.go 文件 代码 253 行。

go func() {// 每个 goroutine 调用 task.Task() 函数,目前不清楚为什么不是使用 task.Task(),可能是语法err := t.Pool.Submit(task.Task)fmt.Println(task.Task)fmt.Println("t.Pool.Submit")if err != nil {t.taskWG.Done()logger.Logger.Error("addTask2Pool ", err)}}()
启动 goroutine 执行操作

参考《Go in Action》第二章:一个 goroutine 是一个独立于其他函数运行的函数。使用关键字 go 启动一个 goroutine,并对这个 goroutine 做并发调度。代码使用关键字 go 启动了一个匿名函数作为 goroutine,这样可以并发地独立处理每个数据源的数据。

匿名函数内部包含了每个 goroutine 要完成的任务,此处是 t.Pool.Submit(task.Task)。

参考文章:ants:在Submit中再调用当前Pool的Submit可能导致阻塞,关于协程池 Pool。
goroutine pool减小开销的主要思路就是复用。即创建出的goroutine在做完一个task后不退出,而是等待下一个task,这样来减少goroutine反复创建和销毁带来的开销。

goroutine 的操作:task.Task

查看每个 goroutine 执行的操作,分析代码部分见注释内容。

// task_main.go 代码 264 行
/**
单个运行的tab标签任务,实现了workpool的接口
*/
func (t *tabTask) Task() {// 《Go in Action》第二章:// 关键字 defer 会安排随后的函数调用在函数返回时才执行。// taskWG.Done():任务完成后计数器减一defer t.crawlerTask.taskWG.Done()// 调用 engine2.NewTab()配置 tab,然后执行 tab// 这些参数不是与请求强相关的参数,可以忽略不看tab := engine2.NewTab(t.browser, *t.req, engine2.TabConfig{TabRunTimeout:           t.crawlerTask.Config.TabRunTimeout,DomContentLoadedTimeout: t.crawlerTask.Config.DomContentLoadedTimeout,EventTriggerMode:        t.crawlerTask.Config.EventTriggerMode,EventTriggerInterval:    t.crawlerTask.Config.EventTriggerInterval,BeforeExitDelay:         t.crawlerTask.Config.BeforeExitDelay,EncodeURLWithCharset:    t.crawlerTask.Config.EncodeURLWithCharset,IgnoreKeywords:          t.crawlerTask.Config.IgnoreKeywords,CustomFormValues:        t.crawlerTask.Config.CustomFormValues,CustomFormKeywordValues: t.crawlerTask.Config.CustomFormKeywordValues,})// 执行 tab,具体分析查看本章第四小节:发送请求的 tab 任务tab.Start()// 收集结果t.crawlerTask.Result.resultLock.Lock()t.crawlerTask.Result.AllReqList = append(t.crawlerTask.Result.AllReqList, tab.ResultList...)t.crawlerTask.Result.resultLock.Unlock()for _, req := range tab.ResultList {if t.crawlerTask.Config.FilterMode == config.SimpleFilterMode {if !t.crawlerTask.smartFilter.SimpleFilter.DoFilter(req) {t.crawlerTask.Result.resultLock.Lock()t.crawlerTask.Result.ReqList = append(t.crawlerTask.Result.ReqList, req)t.crawlerTask.Result.resultLock.Unlock()if !engine2.IsIgnoredByKeywordMatch(*req, t.crawlerTask.Config.IgnoreKeywords) {t.crawlerTask.addTask2Pool(req)}}} else {if !t.crawlerTask.smartFilter.DoFilter(req) {t.crawlerTask.Result.resultLock.Lock()t.crawlerTask.Result.ReqList = append(t.crawlerTask.Result.ReqList, req)t.crawlerTask.Result.resultLock.Unlock()if !engine2.IsIgnoredByKeywordMatch(*req, t.crawlerTask.Config.IgnoreKeywords) {t.crawlerTask.addTask2Pool(req)}}}}
}
配置引擎的库:/pkg/engine

查看 engine 库的目录结构,结合网上搜索结果,推测是作者自己编写的库。简单查看后,发现该库不重要,接下来看 tab 任务。

在这里插入图片描述

查看 task_main.go 文件与 engine 相关的调用代码。

import (engine2 "crawlergo/pkg/engine")type CrawlerTask struct {Browser       *engine2.Browser    //
}type tabTask struct {crawlerTask *CrawlerTaskbrowser     *engine2.Browser
}// 代码147行
crawlerTask.Browser = engine2.InitBrowser(taskConf.ChromiumPath, taskConf.IncognitoContext, taskConf.ExtraHeaders, taskConf.Proxy, taskConf.NoHeadless)
发送请求的 tab 任务
结构体:tab

定义的 tab 结构体如下,先不管执行到 tab.Start() 时装配了哪些变量。

// task_main.go 文件
type tabTask struct {crawlerTask *CrawlerTaskbrowser     *engine2.Browserreq         *model.Requestpool        *ants.Pool
}
执行操作函数:tab.Start()

函数位置:/pkg/engine/tab.go 代码 188 行。

处理返回结果的主要代码:

  • tab.collectLinkWG.Add(3)
  • go tab.collectLinks(),函数位于 colletc_links.go
  • tab.collectLinkWG.Wait()
func (tab *Tab) Start() {// 打印提示信息logger.Logger.Info("Crawling " + tab.NavigateReq.Method + " " + tab.NavigateReq.URL.String())// 执行之后调用 tab.Cancel()defer tab.Cancel()if err := chromedp.Run(*tab.Ctx,RunWithTimeOut(tab.Ctx, tab.config.DomContentLoadedTimeout, chromedp.Tasks{//runtime.Enable(),// 开启网络层APInetwork.Enable(),// 开启请求拦截APIfetch.Enable().WithHandleAuthRequests(true),// 添加回调函数绑定// XSS-Scan 使用的回调runtime.AddBinding("addLink"),runtime.AddBinding("Test"),// 初始化执行JSchromedp.ActionFunc(func(ctx context.Context) error {var err error_, err = page.AddScriptToEvaluateOnNewDocument(js.TabInitJS).Do(ctx)if err != nil {return err}return nil}),network.SetExtraHTTPHeaders(tab.ExtraHeaders),// 执行导航//chromedp.Navigate(tab.NavigateReq.URL.String()),chromedp.ActionFunc(func(ctx context.Context) error {_, _, _, err := page.Navigate(tab.NavigateReq.URL.String()).Do(ctx)if err != nil {return err}return waitNavigateDone(ctx)}),}),); err != nil {if err.Error() == "context canceled" {return}logger.Logger.Warn("navigate timeout ", tab.NavigateReq.URL.String())}go func() {// 等待所有协程任务结束tab.WG.Wait()tab.NavDone <- 1}()select {case <-tab.NavDone:logger.Logger.Debug("all navigation tasks done.")case <-time.After(tab.config.DomContentLoadedTimeout + time.Second*10):logger.Logger.Warn("navigation tasks TIMEOUT.")}// 等待收集所有链接logger.Logger.Debug("collectLinks start.")tab.collectLinkWG.Add(3)go tab.collectLinks()tab.collectLinkWG.Wait()logger.Logger.Debug("collectLinks end.")// 识别页面编码 并编码所有URLif tab.config.EncodeURLWithCharset {tab.DetectCharset()tab.EncodeAllURLWithCharset()}//fmt.Println(tab.NavigateReq.URL.String(), len(tab.ResultList))//for _, v := range tab.ResultList {//	v.SimplePrint()//}// fmt.Println("Finished " + tab.NavigateReq.Method + " " + tab.NavigateReq.URL.String())
}
处理响应信息:collect_links.go(分析方向的转折点)

查看相关的调用函数:tab.GetExecutor(),追溯到 tab.go 文件 327 行,看不太懂。

下列代码看了半天,没发现类似 http.Get() 的函数 调用。全文搜索 .Get(.Post(,发现只有获取 robots.txt 和 Fuzz字典时会用到这些请求。

简单看了一些对响应信息的处理,突然意识到以无头模式启动 Google 并且进行复杂的处理方式,是针对爬虫而言的,参数FuzzPoc验证 没必要使用无头模式,唯一需要的是借鉴项目的并发代码 。

/**
最后收集所有的链接
*/
func (tab *Tab) collectLinks() {go tab.collectHrefLinks()go tab.collectObjectLinks()go tab.collectCommentLinks()
}func (tab *Tab) collectHrefLinks() {defer tab.collectLinkWG.Done()ctx := tab.GetExecutor()fmt.Println("打印信息:")// 打印结果:context.Background.WithCancel.WithValue(type chromedp.contextKey, val <not Stringer>).WithCancel.WithValue(type chromedp.contextKey, val <not Stringer>).WithCancel.WithValue(type chromedp.contextKey, val <not Stringer>).WithDeadline(2022-04-13 19:16:13.5023884 +0800 CST m=+20.528509501 [16.1644673s]).WithValue(type cdp.contextKey, val <not Stringer>)//fmt.Println(ctx)// 收集 src href data-url 属性值attrNameList := []string{"src", "href", "data-url", "data-href"}for _, attrName := range attrNameList {tCtx, cancel := context.WithTimeout(ctx, time.Second*1)var attrs []map[string]string_ = chromedp.AttributesAll(fmt.Sprintf(`[%s]`, attrName), &attrs, chromedp.ByQueryAll).Do(tCtx)/* 打印结果[map[src:../images/Less-1.jpg]][][][]*/fmt.Println(attrs)cancel()for _, attrMap := range attrs {tab.AddResultUrl(config.GET, attrMap[attrName], config.FromDOM)}}
}

小结

简单看了一些对响应信息的处理,突然意识到以无头模式启动 Google 并且进行复杂的处理方式,是针对爬虫而言的,参数FuzzPoc验证 没必要使用无头模式,唯一需要的是借鉴项目的并发代码 。

接下来另开一篇文章,搞清除并发代码,然后应用到 参数Fuzz 模块即可。

这篇关于【毕设扫描器】【参数Fuzz】第二篇:动态爬虫的创建、启动和协程池的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/621799

相关文章

Window Server创建2台服务器的故障转移群集的图文教程

《WindowServer创建2台服务器的故障转移群集的图文教程》本文主要介绍了在WindowsServer系统上创建一个包含两台成员服务器的故障转移群集,文中通过图文示例介绍的非常详细,对大家的... 目录一、 准备条件二、在ServerB安装故障转移群集三、在ServerC安装故障转移群集,操作与Ser

Window Server2016 AD域的创建的方法步骤

《WindowServer2016AD域的创建的方法步骤》本文主要介绍了WindowServer2016AD域的创建的方法步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、准备条件二、在ServerA服务器中常见AD域管理器:三、创建AD域,域地址为“test.ly”

MySQL中时区参数time_zone解读

《MySQL中时区参数time_zone解读》MySQL时区参数time_zone用于控制系统函数和字段的DEFAULTCURRENT_TIMESTAMP属性,修改时区可能会影响timestamp类型... 目录前言1.时区参数影响2.如何设置3.字段类型选择总结前言mysql 时区参数 time_zon

Python如何使用seleniumwire接管Chrome查看控制台中参数

《Python如何使用seleniumwire接管Chrome查看控制台中参数》文章介绍了如何使用Python的seleniumwire库来接管Chrome浏览器,并通过控制台查看接口参数,本文给大家... 1、cmd打开控制台,启动谷歌并制定端口号,找不到文件的加环境变量chrome.exe --rem

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

Python在固定文件夹批量创建固定后缀的文件(方法详解)

《Python在固定文件夹批量创建固定后缀的文件(方法详解)》文章讲述了如何使用Python批量创建后缀为.md的文件夹,生成100个,代码中需要修改的路径、前缀和后缀名,并提供了注意事项和代码示例,... 目录1. python需求的任务2. Python代码的实现3. 代码修改的位置4. 运行结果5.

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.

使用SpringBoot创建一个RESTful API的详细步骤

《使用SpringBoot创建一个RESTfulAPI的详细步骤》使用Java的SpringBoot创建RESTfulAPI可以满足多种开发场景,它提供了快速开发、易于配置、可扩展、可维护的优点,尤... 目录一、创建 Spring Boot 项目二、创建控制器类(Controller Class)三、运行

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R