分析某款go端口扫描器之二

一、概述

本次主要分析指纹识别部分,针对开放http的端口的服务信息进行识别。

项目来源:https://github.com/XinRoom/go-portScan/blob/main/util/file.go

二、core--port-fingerprint-webfinger目录

1、fingerprint-webfinger-faviconhash.go

此文件的功能,主要是从html页面中提取favicon的url,然后计算favicon的hash值

此文件中主要包含三个方法:

  • func FindFaviconUrl(body string) string

从 HTML 页面的字符串中提取 favicon 的 URL;使用正则表达式 shortcutText 匹配包含 favicon 的 标签,然后再使用 shortcutHref 提取其 href 属性中的 URL

  • func mmh3Hash32(raw []byte) string

计算给定字节切片的 MurmurHash3 32 位哈希值;使用 MurmurHash3 算法对输入的字节切片进行哈希运算,返回结果作为字符串表示。

  • func standBase64(braw []byte) []byte

对二进制数据进行标准的 Base64 编码,并按行分割。将输入的二进制数据使用标准的 Base64 编码成字符串,然后按照每行字符数为 76 的标准,将编码后的结果分割成多行。

package webfinger

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"github.com/twmb/murmur3"
	"regexp"
)

var (
	shortcutText = regexp.MustCompile(`(?im)`)//提取link标签的正则表达式
	shortcutHref = regexp.MustCompile(`(?im)href=['"]+(.*?)['"]+`)//提取link标签里的href值的正则表达式
)

func FindFaviconUrl(body string) string {
	a := shortcutText.FindStringSubmatch(body)//首先根据正则提取传入的html页面的link标签,赋值给a
	if len(a) > 0 {//如果存在link标签
		faviconLink := a[0]
		b := shortcutHref.FindStringSubmatch(faviconLink)//提取第一个link标签的href的值
		if len(b) > 1 {
			return b[1] //如果提取url成功,则返回url
		}
	}
	return ""//不存在link标签,返回空
}

func mmh3Hash32(raw []byte) string {
	var h32 = murmur3.New32()
	_, err := h32.Write(raw)
	if err == nil {
		return fmt.Sprintf("%d", int32(h32.Sum32()))//使用 MurmurHash3 算法对输入的字节切片进行哈希运算,返回结果作为字符串表示。
	} else {
		return ""
	}
}

func standBase64(braw []byte) []byte {
	bckd := base64.StdEncoding.EncodeToString(braw)
	var buffer bytes.Buffer
	for i := 0; i < len(bckd); i++ {
		ch := bckd[i]
		buffer.WriteByte(ch)
		if (i+1)%76 == 0 {
			buffer.WriteByte('\n')//将输入的二进制数据使用标准的 Base64 编码成字符串,然后按照每行字符数为 76 的标准,将编码后的结果分割成多行。
		}
	}
	buffer.WriteByte('\n')
	return buffer.Bytes()
}

2、fingerprint-webfinger-finger.json

这个文件主要为faviconhash的字典,用来通过faviconhash的值比对来获取指纹信息。

分析某款go端口扫描器之二_第1张图片

3、fingerprint-webfinger-finger.go

此文件代码是一个 Web 系统指纹识别器。它的主要目的是从 HTTP 响应或 Favicon.ico 数据中识别 Web 服务的指纹。下面是一步步的解释:

  • type Date struct
  • type WebFinger struct 

Date 结构体表示特定指纹识别方法的数据结构;

WebFinger 结构体用于保存不同的指纹集合

type Date struct {
	Name     string //指纹名称
	Location string  //在http响应中的位置
	Method   string  //指纹识别方法(如keyword、regular、faviconhash)
	Keyword  []string //关键字或者正则表达式数组
}

type WebFinger struct {
	Name    string    //指纹集合的名称
	Fingers []Date    //包含data结构体的切片
}

var WebFingers []WebFinger

//go:embed finger.json
var DefFingerData []byte
  • func ParseWebFingerData(data []byte) error

解析 Web 指纹数据。

func ParseWebFingerData(data []byte) error {
    err := json.Unmarshal(data, &WebFingers)  //将json数据保存到WebFingers实例中
    if err != nil {
        return err
    }
    return nil
}
  • func LoadWebFingerData(file string) error

从文件中加载 Web 指纹数据。

//LoadWebFingerData 加载web指纹数据
func LoadWebFingerData(file string) error {

    data, err := os.ReadFile(file)
    if err != nil {
        return err
    }
    err = ParseWebFingerData(data)
    if err != nil {
        return err
    }
    return nil
}
  • func web FingerIdent(resp *http.Response) (names []string)

此函数解析 HTTP 响应的内容和头部信息,然后遍历 WebFingers 中的指纹集合,检查是否匹配给定的指纹识别方法和关键字或正则表达式;如果匹配,则返回匹配的指纹名称。 

  •  解析 HTTP 响应的内容和头部信息。
  • 遍历 WebFingers 中的指纹集合,检查是否匹配给定的指纹识别方法和关键字或正则表达式。
  • 如果匹配,则返回匹配的指纹名称。
//WebFingerIdent web系统指纹识别 用指纹库的指纹循环比对response中header部分和body部分是否包含特征
func WebFingerIdent(resp *http.Response) (names []string){
    var dataMap = make(map[string]string)
    body, _ := io.ReadAll(resp.Body)
    dataMap["body"] = string(body)
    var b bytes.Buffer
    resp.Header.Write(&b)
    dataMap["header"] = b.String()
    for _,finger :=  range WebFingers {
        for _, finger2 := range finger.Fingers {
            var flag bool
            if _, ok := dataMap[finger2.Location]; !ok {//取指纹库中Location字段为body和header部分的指纹
                continue
            }
            switch finger2.Method {
                case "keyword" :
                    if iskeyword(dataMap[finger2.Location], finger2.Keyword){
                        flag = true
                    }
                case "regular" :
                    if isregular(dataMap[finger2.Location], finger2.Keyword){
                        flag = true
                    }
            }
            if flag {
                if finger2.Name != ""{
                    finger.Name += "," + finger2.Name
                }
                names = append(names, finger.Name)
                break
            }
        }
    }
    return
    
}
  • func WebFingerIdentByFavicon(body []byte) (names []string)

 此函数通过 Favicon.ico 识别 Web 系统的指纹。首先对对 Favicon.ico 数据进行哈希运算,然后遍历WebFingers 中的指纹集合,检查是否与哈希值匹配指定的指纹识别方法和关键字。如果匹配,则返回匹配的指纹名称。

  • 对 Favicon.ico 数据进行哈希运算。
  • 遍历 WebFingers 中的指纹集合,检查是否与哈希值匹配指定的指纹识别方法和关键字。
  • 如果匹配,则返回匹配的指纹名称。
// WebFingerIdentByFavicon web系统指纹识别,通过Favicon.ico用指纹库的指纹循环比对favico hash之判断是否包含特征
func WebFingerIdentByFavicon(body []byte) (names []string) {
	var data string
	data = mmh3Hash32(standBase64(body))
	for _, finger := range WebFingers {
		for _, finger2 := range finger.Fingers {
			switch finger2.Method {
			case "faviconhash":
				if data != "" && len(finger2.Keyword) > 0 && data == finger2.Keyword[0] {
					if finger2.Name != "" {
						finger.Name += "," + finger2.Name
					}
					names = append(names, finger.Name)
					break
				}
			}
		}
	}
	return
}

4、fingerprint-webfinger-matchfinger.go

此文件包含两个方法,iskeyword和isregular,都是用于检查字符串中是否存在特定的关键字或匹配正则表达式

  • func iskeyword(str string, keyword []string) bool

检查字符串中是否存在指定的关键字。遍历关键字数组,如果字符串中不包含其中任何一个关键字,则立即返回 false,否则返回 true

  • func isregular(str string, keyword []string) bool

检查字符串是否匹配指定的正则表达式。遍历正则表达式数组,使用 regexp.MustCompile 创建正则表达式对象,然后检查字符串是否与每个正则表达式匹配。如果字符串不匹配任何一个正则表达式,则立即返回 false,否则返回 true

func iskeyword(str string, keyword []string) bool {
	if len(keyword) == 0 || str == "" {//先判断传入关键字切片是否为空和字符串是否为空
		return false
	}
    //遍历关键字数组,如果字符串中不包含其中任何一个关键字,则立即返回 
	for _, k := range keyword {//循环遍历keyword切片,检查keyword是否都包含在str字符串中
		if !strings.Contains(str, k) {
			return false
		}
	}
	return true
}

//遍历正则表达式数组,使用 regexp.MustCompile 创建正则表达式对象,然后检查字符串是否与每个正则表达式匹配。如果字符串不匹配任何一个正则表达式,则立即返回 false,否则返回 true。
func isregular(str string, keyword []string) bool {
	if len(keyword) == 0 || str == "" {
		return false
	}
	for _, k := range keyword {
		re := regexp.MustCompile(k)
		if !re.Match([]byte(str)) {
			return false
		}
	}
	return true
}

5、辅助函数

  • mmh3Hash32(raw []byte) string:对数据进行哈希并返回哈希值的字符串表示形式。
  • standBase64(braw []byte) []byte:对数据进行 Base64 编码,并在每行末尾添加换行符。

三、core-port-fingerprint目录

1、core-port-fingerprint-encodings.go

此文件主要是一些用于字符编码转换的函数,以及一个用于从http响应中提取标题并根据响应的内容类型进行相应的解码的函数。

  • func Decodegbk(s []byte) ([]byte, error)

这个函数用于将 GBK 编码的字节序列转换为 UTF-8 编码的字节序列。它使用 simplifiedchinese.GBK.NewDecoder() 进行解码,将输入的 GBK 编码字节流转换为 UTF-8 编码

//将GBK编码转化UTF-编码
func Decodegbk(s []byte) ([]byte, error){
    I := bytes.NewReader(s)
    O := transform.NewReader(I, simplifiedchinese.GBK.NewDecoder())
    d, e := ioutil.ReadAll(O)
    if e != nil {
        return nil, e
    }
    return d, nil

}
  • func Decodebig5(s []byte) ([]byte, error)

类似于 Decodegbk,这个函数将 BIG5 编码的字节序列转换为 UTF-8 编码的字节序列,使用 traditionalchinese.Big5.NewDecoder() 进行解码。

func Decodebig5(s []byte) ([]byte, error){
    I := bytes.NewReader(s)
    O := transform.NewReader(I, traditionalchinese.Big5.NewDecoder())
    d, e := ioutil.ReadAll(O)
    if e != nil {
        return nil, e
    }
    return d, nil
}

  • func Encodebig5(s []byte) ([]byte, error)

这个函数是将 UTF-8 编码的字节序列转换为 BIG5 编码的字节序列,使用 traditionalchinese.Big5.NewEncoder() 进行编码。

func Encodebig5(s []byte) ([]byte, error){
    I := bytes.NewReader(s)
    O := transform.NewReader(I, traditionalchinese.Big5.NewEncoder())
    d, e := ioutil.ReadAll(O)
    if e != nil {
        return nil, e 
    }
    return d,nil
}
  • func DecodeKorean(s []byte) ([]byte, error)

这个函数是用于将韩文编码的字节序列转换为 UTF-8 编码的字节序列,使用 korean.EUCKR.NewDecoder() 进行解码。

func DecodeKorean(s []byte) ([]byte, error) {
    koreanDecoder := korean.EUCKR.NewDecoder()
    return koreanDecoder.Bytes(s)
}
  • func DecodeData(data []byte, headers http.Header) ([]byte, error)

这个函数从 HTTP 响应中提取数据,并根据响应的内容类型进行相应的解码。它会检查响应头中的 Content-Type,尝试根据特定的字符集(如 GBK、EUC-KR 等)对数据进行解码。如果检测到响应头中指定了字符集,会调用相应的解码函数进行转换,如果没有匹配到特定的字符集,就会尝试从 HTML 头部的 meta 标签中提取字符集信息,并根据提取的信息进行解码。最终返回解码后的数据或原始数据(如果未指定字符集或解码失败)。

func DecodeData(data []byte, headers http.Header) ([]byte, error) {

    //Non UTF-8
    if contentTypes, ok := headers["Content-Type"]; ok {
        contentType := strings.ToLower(strings.Join(contentTypes, ";"))//用;连接contenttypes内容,并转化为小写
        
        //根据不同的contentyps值调用不同的解密方法
        switch {
            case stringsutil.ContainsAny(contentType, "charset=gb2312","charset=gbk"):
                return Decodegbk([]byte(data))

            case stringsutil.ContainsAyn(contentType,"euc-kr"):
                return DecodeDorean(data)    
        }
        
        //Content-Type 来自 head tag
        var match = reContentType.FindSubmatch(data)//reContentType来自 core/port/fingerprint/title.go ,作用为regexp.MustCompile(`(?im)\s*charset="(.*?)"|charset=(.*?)"\s*`),根据正则提取对应字段
        var mcontentType = ""
        if len(match) != 0 {
            for i, v := range match{
                if string(v) != "" && i != 0 {
                    mcontentType = string(v)
                }
            }
            mcontentType = strings.ToLower(mcontentType)
        }
        switch {
            case stringsutil.ContainsAny(mcontentType, "gb2312", "gbk"):
                return Decodegbk(data)
        }
    }
    return data, nil
        
}    

ps:这些函数的作用在于根据不同的字符编码格式,将原始的字符序列转换为 UTF-8 编码的字符序列,使其能够被正确解析和处理。DecodeData 函数则是根据 HTTP 响应的内容类型来判断使用何种字符编码转换函数,以确保正确地解码数据

2、core-port-fingerprint-fingerprint.go

这个文件主要用于端口服务的指纹识别,主要功能如下:

首先创建常量和类型:

type Action uint8

const (
	ActionRecv = Action(iota)
	ActionSend
)

const (
	refusedStr   = "refused"
	ioTimeoutStr = "i/o timeout"
)

type ruleData struct {
	Action  Action // send or recv
	Data    []byte // send or match data
	Regexps []*regexp.Regexp
}

type serviceRule struct {
	Tls       bool
	DataGroup []ruleData
}

var serviceRules = make(map[string]serviceRule)
var readBufPool = &sync.Pool{
	New: func() interface{} {
		return make([]byte, 4096)
	},
}

函数如下:

  • func PortIdentify(network string, ip net.IP, _port uint16, dailTimeout time.Duration) (serviceName string, isDailErr bool)

此函数识别端口对应的服务。它按照指定的顺序尝试从一系列服务规则中匹配出对应的服务。如果连接失败或者无法匹配出服务,则返回"unknown"

// PortIdentify 端口识别
func PortIdentify(network string, ip net.IP, _port uint16, dailTimeout time.Duration) (serviceName string, isDailErr bool) {

	matchedRule := make(map[string]struct{})

	unknown := "unknown"
	var matchStatus int

	// 优先判断port可能的服务
	if serviceNames, ok := portServiceOrder[_port]; ok {//portServiceOrder为rule.go文件中常见端口服务对应列表
		for _, service := range serviceNames {
			matchedRule[service] = struct{}{}
			matchStatus = matchRule(network, ip, _port, serviceRules[service], dailTimeout)//获取服务端口开放状态
			if matchStatus == 1 {
				return service, false
			} else if matchStatus == -1 {
				return unknown, true
			}
		}
	}

	// onlyRecv
	{
		var conn net.Conn
		var n int
		buf := readBufPool.Get().([]byte)
		defer func() {
			readBufPool.Put(buf)
		}()
		address := fmt.Sprintf("%s:%d", ip, _port)
		conn, _ = net.DialTimeout(network, address, dailTimeout)
		if conn == nil {
			return unknown, true
		}
		n, _ = read(conn, buf)
		conn.Close()
		if n != 0 {
			for _, service := range onlyRecv {
				_, ok := matchedRule[service] //检测是不是已有服务
				if ok {
					continue
				}
				matchStatus = matchRuleWhithBuf(buf[:n], ip, _port, serviceRules[service])//判断端口服务开放状态
				if matchStatus == 1 {
					return service, false
				}
			}
		}
		for _, service := range onlyRecv {
			matchedRule[service] = struct{}{}
		}
	}

	// 优先判断Top服务
	for _, service := range serviceOrder {
		_, ok := matchedRule[service]
		if ok {
			continue
		}
		matchedRule[service] = struct{}{}
		matchStatus = matchRule(network, ip, _port, serviceRules[service], dailTimeout)
		if matchStatus == 1 {
			return service, false
		} else if matchStatus == -1 {
			return unknown, true
		}
	}

	// other
	for service, rule := range serviceRules {
		_, ok := matchedRule[service]
		if ok {
			continue
		}
		matchStatus = matchRule(network, ip, _port, rule, dailTimeout)
		if matchStatus == 1 {
			return service, false
		} else if matchStatus == -1 {
			return unknown, true
		}
	}

	return unknown, false
}

  • func matchRuleWhithBuf(buf, ip net.IP, _port uint16, serviceRule serviceRule) int

函数根据传入的数据缓冲区,匹配给定的服务规则。如果规则中的数据在缓冲区中找到了匹配,则返回成功状态。

// 指纹匹配函数
func matchRuleWhithBuf(buf, ip net.IP, _port uint16, serviceRule serviceRule) int {
	data := []byte("")
	// 逐个判断
	for _, rule := range serviceRule.DataGroup {
		if rule.Data != nil {//先替换请求中的数据data数据,比如IP和port
			data = bytes.Replace(rule.Data, []byte("{IP}"), []byte(ip.String()), -1)
			data = bytes.Replace(data, []byte("{PORT}"), []byte(strconv.Itoa(int(_port))), -1)
		}
		// 包含数据就正确,使用正则匹配
		if rule.Regexps != nil {
			for _, _regex := range rule.Regexps {
				if _regex.MatchString(convert2utf8(string(buf))) {
					return 1
				}
			}
		}
		if bytes.Compare(data, []byte("")) != 0 && bytes.Contains(buf, data) {
			return 1
		}
	}
	return 0
}

  • func matchRule(network string, ip net.IP, _port uint16, serviceRule serviceRule, dailTimeout time.Duration) int

此函数建立连接并根据连接的情况进行数据的发送和接收,然后匹配给定的服务规则。如果规则中的数据在接收的数据中找到了匹配,则返回成功状态。

// 指纹匹配函数
func matchRule(network string, ip net.IP, _port uint16, serviceRule serviceRule, dailTimeout time.Duration) int {
	var err error
	var isTls bool
	var conn net.Conn
	var connTls *tls.Conn

	address := fmt.Sprintf("%s:%d", ip, _port)

	// 建立连接
	if serviceRule.Tls {//先确定是不是https
		// tls
		connTls, err = tls.DialWithDialer(&net.Dialer{Timeout: dailTimeout}, network, address, &tls.Config{
			InsecureSkipVerify: true,
			MinVersion:         tls.VersionTLS10,
		})
		if err != nil {
			if strings.HasSuffix(err.Error(), ioTimeoutStr) || strings.Contains(err.Error(), refusedStr) {
				return -1
			}
			return 0
		}
		defer connTls.Close()
		isTls = true
	} else {//如果不是https,使用net.conn连接
		conn, err = net.DialTimeout(network, address, dailTimeout)
		if conn == nil {
			return -1
		}
		defer conn.Close()
	}

	buf := readBufPool.Get().([]byte)
	defer func() {
		readBufPool.Put(buf)
	}()

	data := []byte("")
	// 逐个判断
	for _, rule := range serviceRule.DataGroup {
		if rule.Data != nil {//替换所有data数据中的IP和port,针对http和https
			data = bytes.Replace(rule.Data, []byte("{IP}"), []byte(ip.String()), -1)
			data = bytes.Replace(data, []byte("{PORT}"), []byte(strconv.Itoa(int(_port))), -1)
		}

		if rule.Action == ActionSend {//先判断获取发送数据,进行conn连接
			if isTls {
				connTls.SetWriteDeadline(time.Now().Add(time.Second))
				_, err = connTls.Write(data)
			} else {
				conn.SetWriteDeadline(time.Now().Add(time.Second))
				_, err = conn.Write(data)
			}
			if err != nil {
				// 出错就退出
				return 0
			}
		} else {//针对recive进行数据判断
			var n int
			if isTls {
				n, err = read(connTls, buf)
			} else {
				n, err = read(conn, buf)
			}
			// 出错就退出
			if n == 0 {
				return 0
			}
			// 包含数据就正确  使用正则筛选返回包数据
			if rule.Regexps != nil {
				for _, _regex := range rule.Regexps {
					if _regex.MatchString(convert2utf8(string(buf[:n]))) {
						return 1
					}
				}
			}
            //判断数据是否为空,以及buf中是否包含数据
			if bytes.Compare(data, []byte("")) != 0 && bytes.Contains(buf[:n], data) {
				return 1
			}
		}
	}

	return 0
}

  • func read(conn interface{}, buf []byte) (int, error)

函数负责从连接中读取数据,并在指定时间内设置读取的截止时间。它会尝试从传入的连接中读取数据到缓冲区中,并返回读取的字节数和可能出现的错误。如果读取的数据长度为 0 或者发生错误,则会返回相应的状态信息。

func read(conn interface{}, buf []byte) (int, error) {
	switch conn.(type) {
	case net.Conn:
		conn.(net.Conn).SetReadDeadline(time.Now().Add(time.Second))
		return conn.(net.Conn).Read(buf[:])
	case *tls.Conn:
		conn.(*tls.Conn).SetReadDeadline(time.Now().Add(time.Second))
		return conn.(*tls.Conn).Read(buf[:])
	}
	return 0, errors.New("unknown type")
}

  • func convert2utf8(src string) string

函数用于修复正则表达式在匹配非UTF-8字符时可能出现的问题。它会将UTF-8以外的字符转换成对应的UTF-8字符,确保正则表达式可以正确匹配。

func convert2utf8(src string) string {
	var dst string
	for i, r := range src {
		var v string
		if r == utf8.RuneError {
			// convert, rune => string, intstring() => encoderune()
			v = string(src[i])
		} else {
			v = string(r)
		}
		dst += v
	}
	return dst
}

 ps:代码中利用 sync.Pool 作为缓冲池,用来存储读取数据的缓冲区,提高效率。总体来说,这些函数实现了通过发送和接收数据并根据特定规则匹配服务的功能,用于识别特定端口上可能运行的服务

3、core-port-fingerprint-rules.go

这个文件主要为一个指纹识别器,它尝试根据不同服务的特征来判断特定端口上的服务。这里的 serviceOrder 是服务的顺序列表,onlyRecv 是仅接收数据的服务列表,portServiceOrder 是特定端口和服务的映射关系。

var serviceOrder = []string{"http", "https", "ssh", "redis", "mysql"}

var onlyRecv []string

var portServiceOrder = map[uint16][]string{
	21:    {"ftp"},
	22:    {"ssh"},
	80:    {"http", "https"},
	443:   {"https", "http"},
	445:   {"smb"},
	1035:  {"oracle"},
	1080:  {"socks5", "socks4"},
	1081:  {"socks5", "socks4"},
	1082:  {"socks5", "socks4"},
	1083:  {"socks5", "socks4"},
	1433:  {"sqlserver"},
	1521:  {"oracle"},
	1522:  {"oracle"},
	1525:  {"oracle"},
	1526:  {"oracle"},
	1574:  {"oracle"},
	1748:  {"oracle"},
	1754:  {"oracle"},
	3306:  {"mysql"},
	3389:  {"ms-wbt-server"},
	6379:  {"redis"},
	9001:  {"mongodb"},
	11211: {"memcached"},
	14238: {"oracle"},
	27017: {"mongodb"},
	20000: {"oracle"},
	49153: {"mongodb"},
}
  • func init()

init() 函数中,为了不同的服务(如HTTP、HTTPS、SSH等),定义了对应的识别规则。每个服务都有其特定的数据发送和接收规则,用于匹配返回的数据以确定服务是否存在。例如,对于 HTTP 服务,它尝试发送一个 HTTP 头部,并尝试匹配返回数据中是否包含 HTTP/;对于 SSH 服务,它会尝试匹配返回数据中是否包含 SSH 相关的标识等。这些规则都是通过发送特定数据和匹配接收到的数据来进行识别的

func init() {
    
	// http
	serviceRules["http"] = serviceRule{ //pingerprint.go中结构体,包含Tls和DataGroup
		Tls: false,
		DataGroup: []ruleData{
			{//设置http和https的规则数据,包含请求头和接收包
				ActionSend,
				[]byte("HEAD / HTTP/1.1\r\nHost: {IP}\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0\r\nAccept: */*\r\nAccept-Language: en\r\nAccept-Encoding: deflate\r\n\r\n"),
				nil,
			},
			{
				ActionRecv,
				[]byte("HTTP/"),
				nil,
			},
		},
	}
	// https
	serviceRules["https"] = serviceRule{
		Tls:       true,
		DataGroup: serviceRules["http"].DataGroup,
	}

	// ssh
	serviceRules["ssh"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{//设置ssh的匹配规则
				ActionRecv,
				nil,
				[]*regexp.Regexp{
					regexp.MustCompile(`^SSH-([\d.]+)-`),
					regexp.MustCompile(`^SSH-(\d[\d.]+)-`),
					regexp.MustCompile(`^SSH-(\d[\d.]*)-`),
					regexp.MustCompile(`^SSH-2\.0-`),
					regexp.MustCompile(`^SSH-1\.`),
				},
			},
		},
	}

	// ftp
	serviceRules["ftp"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{//设置ftp的匹配规则
				ActionRecv,
				nil,
				[]*regexp.Regexp{
					regexp.MustCompile(`^220 ([-/.+\w]+) FTP server`),
					regexp.MustCompile(`^220[ |-](.*?)FileZilla`),
					regexp.MustCompile(`^(?i)220[ |-](.*?)version`),
					regexp.MustCompile(`^220 3Com `),
					regexp.MustCompile(`^220-GuildFTPd`),
					regexp.MustCompile(`^220-.*\r\n220`),
					regexp.MustCompile(`^220 Internet Rex`),
					regexp.MustCompile(`^530 Connection refused,`),
					regexp.MustCompile(`^220 IIS ([\w._-]+) FTP`),
					regexp.MustCompile(`^220 PizzaSwitch `),
					regexp.MustCompile(`(?i)^220 ([-.+\w]+) FTP`),
					regexp.MustCompile(`(?i)^220[ |-](.*?)FTP`),
				},
			},
		},
	}

	// socks4
	serviceRules["socks4"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{//设置socks4发送包的数据部分
				ActionSend,
				[]byte("\x04\x01\x00\x16\x7f\x00\x00\x01rooo\x00"),
				nil,
			},
			{//设置socks4接收包的匹配规则
				ActionRecv,
				nil,
				[]*regexp.Regexp{
					regexp.MustCompile(`^\x00\x5a`),
					regexp.MustCompile(`^\x00\x5b`),
					regexp.MustCompile(`^\x00\x5c`),
					regexp.MustCompile(`^\x00\x5d`),
				},
			},
		},
	}

	// socks5
	serviceRules["socks5"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{//设置socks5的发送包数据
				ActionSend,
				[]byte("\x05\x04\x00\x01\x02\x80\x05\x01\x00\x03\x0dwww.baidu.com\x00\x50GET / HTTP/1.0\r\n\r\n"),
				nil,
			},
			{//设置socks5的接收包规则
				ActionRecv,
				nil,
				[]*regexp.Regexp{
					regexp.MustCompile(`^\x05\x00\x05\x01`),
					regexp.MustCompile(`^\x05\x00\x05\x00\x00\x01.{6}HTTP`),
					regexp.MustCompile(`^\x05\x02`),
					regexp.MustCompile(`^\x05\x00`),
				},
			},
		},
	}

	 tls
	//serviceRules["tls"] = serviceRule{
	//	Tls: false,
	//	DataGroup: []ruleData{
	//		{
	//			ActionSend,
	//			[]byte("\x16\x03\x00\x00S\x01\x00\x00O\x03\x00?G\xd7\xf7\xba,\xee\xea\xb2`~\xf3\x00\xfd\x82{\xb9\xd5\x96\xc8w\x9b\xe6\xc4\xdb<=\xdbo\xef\x10n\x00\x00(\x00\x16\x00\x13\x00\x0a\x00f\x00\x05\x00\x04\x00e\x00d\x00c\x00b\x00a\x00`\x00\x15\x00\x12\x00\x09\x00\x14\x00\x11\x00\x08\x00\x06\x00\x03\x01\x00"),
	//			nil,
	//		},
	//		{
	//			ActionRecv,
	//			nil,
	//			[]*regexp.Regexp{
	//				regexp.MustCompile(`^[\x16\x15]\x03\x00`),
	//				regexp.MustCompile(`^[\x16\x15]\x03...\x02`),
	//			},
	//		},
	//	},
	//}

	// smb
	serviceRules["smb"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{//设置smb协议的发送包数据
				ActionSend,
				[]byte("\x00\x00\x00\xa4\xff\x53\x4d\x42\x72\x00\x00\x00\x00\x08\x01\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x06\x00\x00\x01\x00\x00\x81\x00\x02PC NETWORK PROGRAM 1.0\x00\x02MICROSOFT NETWORKS 1.03\x00\x02MICROSOFT NETWORKS 3.0\x00\x02LANMAN1.0\x00\x02LM1.2X002\x00\x02Samba\x00\x02NT LM 0.12\x00\x02NT LANMAN 1.0\x00"),
				nil,
			},
			{//设置smb协议的接收包规则
				ActionRecv,
				nil,
				[]*regexp.Regexp{
					regexp.MustCompile(`MBr\x00\x00\x00\x00\x88\x01@\x00`),
				},
			},
		},
	}

	// ms-wbt-server
	serviceRules["ms-wbt-server"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{
				ActionSend,
				[]byte("\x03\x00\x00*%\xe0\x00\x00\x00\x00\x00Cookie: mstshash=pcpc\r\n\x01\x00\x08\x00\x03\x00\x00\x00"),
				nil,
			},
			{
				ActionRecv,
				nil,
				[]*regexp.Regexp{
					regexp.MustCompile(`\x03\x00\x00.\x0e\xd0\x00\x00\x124\x00`),
				},
			},
		},
	}

	// jdwp
	serviceRules["jdwp"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{
				ActionRecv,
				[]byte("JDWP-Handshake"),
				nil,
			},
		},
	}

	// jdbc
	serviceRules["jdbc"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{
				ActionRecv,
				[]byte("HSQLDB JDBC Network Listener"),
				nil,
			},
		},
	}

	// Db
	// mysql
	serviceRules["mysql"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{
				ActionRecv,
				nil,
				[]*regexp.Regexp{
					regexp.MustCompile(`(?s)^.\x00\x00\x00\xff..Host .* is not allowed to connect to this .* server$`),
					regexp.MustCompile(`^.\x00\x00\x00\xff..Too many connections`),
					regexp.MustCompile(`(?s)^.\x00\x00\x00\xff..Host .* is blocked because of many connection errors`),
					regexp.MustCompile(`(?s)^.\x00\x00\x00\x0a(\d\.[-_~.+:\w]+MariaDB-[-_~.+:\w]+)`),
					regexp.MustCompile(`(?s)^.\x00\x00\x00\x0a(\d\.[-_~.+\w]+)\x00`),
					regexp.MustCompile(`(?s)^.\x00\x00\x00\xffj\x04'[\d.]+' .* MySQL`),
				},
			},
		},
	}
	// redis
	serviceRules["redis"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{
				ActionSend,
				[]byte("GET / HTTP/1.1\r\n"),
				nil,
			},
			{
				ActionRecv,
				nil,
				[]*regexp.Regexp{
					regexp.MustCompile(`-ERR operation not permitted\r\n`),
					regexp.MustCompile(`-ERR wrong number of arguments for 'get' command\r\n`),
				},
			},
		},
	}

	// sqlserver
	serviceRules["sqlserver"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{
				ActionSend,
				[]byte("\x12\x01\x00\x34\x00\x00\x00\x00\x00\x00\x15\x00\x06\x01\x00\x1b\x00\x01\x02\x00\x1c\x00\x0c\x03\x00\x28\x00\x04\xff\x08\x00\x01\x55\x00\x00\x00\x4d\x53\x53\x51\x4c\x53\x65\x72\x76\x65\x72\x00\x48\x0f\x00\x00"),
				nil,
			},
			{
				ActionRecv,
				[]byte("\x04\x01\x00\x25\x00\x00\x01\x00\x00\x00\x15\x00\x06\x01\x00\x1b\x00\x01\x02\x00\x1c\x00\x01\x03\x00\x1d\x00\x00\xff"),
				nil,
			},
		},
	}

	// oracle
	serviceRules["oracle"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{
				ActionSend,
				[]byte("\x00Z\x00\x00\x01\x00\x00\x00\x016\x01,\x00\x00\x08\x00\x7F\xFF\x7F\x08\x00\x00\x00\x01\x00 \x00:\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\xE6\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00(CONNECT_DATA=(COMMAND=version))"),
				nil,
			},
			{
				ActionRecv,
				nil,
				[]*regexp.Regexp{
					regexp.MustCompile(`(?s)^\x00\x20\x00\x00\x02\x00\x00\x00\x016\x00\x00\x08\x00\x7f\xff\x01\x00\x00\x00\x00\x20`),
					regexp.MustCompile(`^\+\x00\x00\x00$`),
					regexp.MustCompile(`^\x00.\x00\x00\x02\x00\x00\x00.*\(IAGENT`),
					regexp.MustCompile(`^..\x00\x00\x04\x00\x00\x00"\x00..\(DESCRIPTION=`),
					regexp.MustCompile(`^\x00.\x00\x00[\x02\x04]\x00\x00\x00.*\(`),
					regexp.MustCompile(`^\x00.\x00\x00[\x02\x04]\x00\x00\x00.*TNSLSNR`),
					regexp.MustCompile(`^\x00,\x00\x00\x04\x00\x00"`),
				},
			},
		},
	}

	// mongodb
	serviceRules["mongodb"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{
				ActionSend,
				[]byte("\x41\x00\x00\x00\x3a\x30\x00\x00\xff\xff\xff\xff\xd4\x07\x00\x00\x00\x00\x00\x00test.$cmd\x00\x00\x00\x00\x00\xff\xff\xff\xff\x1b\x00\x00\x00\x01serverStatus\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"),
				nil,
			},
			{
				ActionRecv,
				nil,
				[]*regexp.Regexp{
					regexp.MustCompile(`(?s)^.*version([: "]+)([.\d]+)"`),
					regexp.MustCompile(`(?s)^\xcb\x00\x00\x00....:0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa7\x00\x00\x00\x01uptime\x00\x00\x00\x00\x00\x00 ` + "`" + `@\x03globalLock\x009\x00\x00\x00\x01totalTime\x00\x00\x00\x00\x7c\xf0\x9a\x9eA\x01lockTime\x00\x00\x00\x00\x00\x00\xac\x9e@\x01ratio\x00!\xc6\$G\xeb\x08\xf0>\x00\x03mem\x00<\x00\x00\x00\x10resident\x00\x03\x00\x00\x00\x10virtual\x00\xa2\x00\x00\x00\x08supported\x00\x01\x12mapped\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01ok\x00\x00\x00\x00\x00\x00\x00\xf0\?\x00$`),
					regexp.MustCompile(`(?s)^.\x00\x00\x00....:0\x00\x00\x01\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\+\x00\x00\x00\x02errmsg\x00\x0e\x00\x00\x00need to login\x00\x01ok\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`),
					regexp.MustCompile(`(?s)^.\x00\x00\x00....:0\x00\x00\x01\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00.\x00\x00\x00\x01ok\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02errmsg\x00.\x00\x00\x00not authorized on`),
				},
			},
		},
	}

	// memcached
	serviceRules["memcached"] = serviceRule{
		Tls: false,
		DataGroup: []ruleData{
			{
				ActionSend,
				[]byte("stats\\n"),
				nil,
			},
			{
				ActionRecv,
				nil,
				[]*regexp.Regexp{
					regexp.MustCompile(`(?s)^STAT pid \d`),
					regexp.MustCompile(`(?s)^ERROR\r\n`),
				},
			},
		},
	}

	// onlyRecv 仅接收数据的服务列表
	for k, m := range serviceRules {
		if len(m.DataGroup) == 1 {
			onlyRecv = append(onlyRecv, k)
		}
	}
}

 ps:总体来说,这个代码段通过发送特定数据并检查返回数据中是否包含特定标识来识别不同服务的存在。如果匹配成功,则确定该服务在特定端口上运行。

4、core-port-fingerprint-title.go

此文件主要处理从 HTML 文档中提取标题以及获取页面重定向的目标 URL。

首先:定义变量,设置获取title、contenttype、refresh、replace的正则

var (
	cutset        = "\n\t\v\f\r"
	reTitle       = regexp.MustCompile(`(?im)<\s*title.*>(.*?)<\s*/\s*title>`)
	reContentType = regexp.MustCompile(`(?im)\s*charset="(.*?)"|charset=(.*?)"\s*`)
	reRefresh     = regexp.MustCompile(`(?im)\s*content=['"]\d;url=['"](.*?)['"]`)
	reReplace     = regexp.MustCompile(`(?im)window\.location\.replace\(['"](.*?)['"]\)`)
)
  • func ExtractTitle(body []byte) (title string)

这个函数用于从 HTML 页面中提取标题。它首先尝试使用 DOM 解析器解析文档,并提取其中的标题。如果解析出错,就使用正则表达式 reTitle 在 HTML 文档中查找标题标签,提取标题内容。最后对提取到的标题进行处理,去除空白字符和特殊符号

func ExtractTitle(body []byte) (title string) {
    //先从dom中获取
    titleDom, err := getTitleWithDom(body)//从dom节点中获取title
    //如果出错,则回退到用正则匹配
    if  err != nil {
        for _, match := range reTitle.FindAllString(string(body), -1) {
            title = match
            break
        }
    }else {
        title = renderNode(titleDom)//将html转换为字符串类型
    }
    title = html.UnescapeString(trimTitleTags(title))

    //删除多余字符
    title = strings.TrimSpace(strings.Trim(title, cutset)) //去掉首尾的特殊字符
    title = strings.ReplaceAll(title, "\n", "")
    title = strings.ReplaceAll(title, "\r", "")
    
    return title
}

  • func getTitleWithDom(body []byte) (*html.Node, error)

这个函数尝试使用 HTML 解析器来解析 HTML 文档,并找到 </code> 标签对应的节点。它通过递归方式遍历 HTML 文档的节点树,在遍历过程中寻找 <code>title</code> 节点</p> <pre><code class="language-Go">func getTitleWithDom(body []byte) (*html.Node, error){ var title *html.Node var crawler func(*html.Node) crawler = func(node *html.Node) {//用于遍历 HTML 文档的节点树,查找 <title> 节点。 if node.Type == html.ElementNode && node.Data == "title" { title = node return } for child := node.FirstChild; child != nil && title == nil; child = child.NextSibling { crawler(child) } } htmlDoc, err := html.Parse(bytes.NewReader(body))//使用 html.Parse 函数解析传入的 HTML 文档,将其转换为一个 HTML 文档对象(htmlDoc) if err != nil { return nil, err } crawler(htmlDoc) if title != nil { return title, nil } return nil, fmt.Errorf("title not found") }</code></pre> <p></p> </blockquote> <ul> <li>func renderNode(n *html.Node) string</li> </ul> <blockquote> <p>这个函数将 HTML 节点转换为字符串形式,使用 <code>html.Render</code> 将节点内容写入缓冲区,然后将缓冲区的内容以字符串形式返回。</p> <pre><code class="language-Go">func renderNode(n *html.Node) string{ var buf bytes.Buffer w := io.Writer(&buf) html.Render(w, n) //nolint return buf.String() }</code></pre> </blockquote> <ul> <li>func trimTitleTags(title string) string</li> </ul> <blockquote> <p>这个函数用于处理提取到的标题字符串,去除 <code><title></code> 标签的开始和结束标记,只返回标题文本内容</p> <pre><code class="language-Go">func trimTitleTags(title string) string { titleBegin := strings.Index(title, ">") titleEnd := strings.Index(title,"</") if titleEnd < 0 || titleBegin < 0 { return title } return title[titleBegin + 1 : titleEnd] }</code></pre> <p></p> </blockquote> <ul> <li>func GetLocation(body []byte) (location string)</li> </ul> <blockquote> <p>这个函数用于从 HTML 文档中获取重定向的目标 URL。它首先使用正则表达式 <code>reRefresh</code> 在文档中查找重定向的目标 URL,如果未找到,则使用正则表达式 <code>reReplace</code> 进行查找。如果找到,就返回第一个匹配到的重定向 URL。</p> <pre><code class="language-Go">func GetLocation(body []byte) (location string) { for _, match := range reRefresh.FindAllStringSubmatch(string(body),1){ location = match[1] break } if location == "" { for _, match := range reReplace.FindAllStringSubmatch(string(body),1){ location = match[1] break } } return }</code></pre> <p></p> </blockquote> <p><em><u><strong><span style="background-color:#a2e043;"> ps:这些函数主要用于解析 HTML 文档,从中提取标题并检索重定向目标 URL。它们结合使用 DOM 解析器和正则表达式来从文档中提取所需的信息。</span></strong></u></em></p> <h3>5、core-port-fingerprint-http.go</h3> <p>这个文件主要设置HTTP客户端和定义功能来处理响应体,识别其编码并限制最大读取大小。包含函数如下:</p> <p>首先定义:</p> <pre><code class="language-Go">var ErrOverflow = errors.New("OverflowMax") type Options struct { }</code></pre> <ul> <li><span style="color:#956fe7;">func newHttpClient(dialTimeout time.Duration) *http.Client</span></li> </ul> <blockquote> <ul> <li>创建并配置了一个具有特定传输配置的HTTP客户端,包括TLS设置、超时、最大连接数和禁用长连接等。</li> <li>设置了一个自定义的 <code>CheckRedirect</code> 函数来限制最大重定向次数为两次。</li> </ul> <pre><code class="language-Go">func newHttpClient(dialTimeout time.Duration) *http.Client { transport := &http.Transport{ TLSClientConfig: &tls.Config{//设置httpclient配置 InsecureSkipVerify: true, MinVersion: tls.VersionTLS10, }, DialContext: (&net.Dialer{ Timeout: dialTimeout, }).DialContext, MaxIdleConnsPerHost: 1, IdleConnTimeout: 100 * time.Millisecond, TLSHandshakeTimeout: 3 * time.Second, ExpectContinueTimeout: 3 * time.Second, DisableKeepAlives: true, ForceAttemptHttp2: false, Proxy: http.ProxyFromEnvironment, } // proxy //if options.ProxyUrl != "" { // proxyUrl, err := url.Parse(options.ProxyUrl) // if err != nil { // log.Fatalln(err) // } // transport.Proxy = http.ProxyURL(proxyUrl) //} return &http.Client{ Timeout : 3 * time.Second, Transport: transport, CheckRedirect: func(req *http.Request, via []*http.Request) error{ if len(via) >= 2{ return errors.New("stopped after 2 redirects") } return nil }, } }</code></pre> <p></p> </blockquote> <ul> <li><span style="color:#956fe7;">func getBody(resp *http.Response) (body []byte, err error)</span></li> </ul> <blockquote> <ul> <li>根据响应头中的 <code>Content-Encoding</code> 字段确定响应体的编码格式。</li> <li>通过使用适当的读取器(例如 <code>gzip.NewReader</code> 和 <code>flate.NewReader</code>)处理不同的压缩格式,如 gzip 和 deflate。</li> <li>通过调用 <code>readMaxSize</code> 函数,限制响应体的最大大小为300KB。</li> </ul> <pre><code class="language-Go">// getBody 识别响应Body的编码,读取body数据 func getBody(resp *http.Response) (body []byte, err error) { if resp.Body == nil || resp.Body == http.NoBody { return } var reader io.Reader switch resp.Header.Get("Content-Encoding") { case "gzip": reader, err = gzip.NewReader(resp.Body) case "deflate": reader = flate.NewReader(resp.Body) //case "br": // reader = brotli.NewReader(resp.Body) default: reader = resp.Body } if err == nil { body, err = readMaxSize(reader, 300*1024) // Max Size 300kb } return }</code></pre> <p></p> </blockquote> <ul> <li><span style="color:#956fe7;">func readMaxSize(r io.Reader, maxsize int) ([]byte, error)</span></li> </ul> <blockquote> <ul> <li>从 <code>io.Reader</code>(<code>r</code>)读取数据,同时限制最大尺寸(<code>maxsize</code>)。</li> <li>为一个字节切片(<code>b</code>)分配内存,并以块的方式读取数据,将其附加到切片中,直到达到最大尺寸。</li> <li>如果达到了尺寸限制,函数返回到目前为止读取的数据以及一个 <code>ErrOverflow</code> 错误。</li> </ul> <pre><code class="language-Go"> //这个函数的作用是安全地从 io.Reader 中读取数据,并在达到指定的最大尺寸时停止读取,以避免读取过多的数据造成内存溢出或其他问题。 func readMaxSize(r io.Reader, maxsize int) ([]byte, error) { b := make([]byte, 0, 512) for { if len(b) >= maxsize { return b, ErrOverflow } if len(b) == cap(b) { // Add more capacity (let append pick how much). b = append(b, 0)[:len(b)] } n, err := r.Read(b[len(b):cap(b)]) b = b[:len(b)+n] if err != nil { if err == io.EOF { err = nil } return b, err } } }</code></pre> <p></p> </blockquote> <p><em><u><strong><span style="background-color:#a2e043;">ps:这些函数为主要目的是创建一个具有特定配置的HTTP客户端,并提供功能来处理响应体的解码,考虑到不同的压缩格式,并限制读取数据的最大尺寸,以防止潜在的过大响应引发问题。</span></strong></u></em></p> <p></p> <p></p> <h3>6、core-port-fingerprint-httpInfo.go</h3> <p>这个文件主要实现了HTTP服务信息的探测和提取。主要函数如下:</p> <p>先定义变量httpsTopPort,高频的https端口</p> <pre><code class="language-Go">var httpsTopPort = []uint16{443, 4443, 1443, 8443} var httpClient *http.Client</code></pre> <ul> <li><span style="color:#956fe7;">func ProbeHttpInfo(ip net.IP, _port uint16, dialTimeout time.Duration) (httpInfo *port.HttpInfo, isDailErr bool)</span></li> </ul> <blockquote> <ul> <li>接收一个 IP、端口和请求超时时间,用于在给定的 IP 地址和端口上探测 HTTP 服务信息。</li> <li>根据指定的端口列表确定探测的协议(HTTP 或 HTTPS)。</li> <li>使用 <code>httpClient</code> 对象发送 HTTP 请求,并根据响应提取信息。</li> <li>通过循环尝试 HTTP 和 HTTPS 请求,根据响应来获取信息,包括状态码、重定向 URL、服务器信息、标题、TLS 证书相关信息等。</li> <li>通过 <code>webfinger</code> 包来分析响应的指纹信息,还会尝试提取页面的 favicon。</li> </ul> <pre><code class="language-Go">func ProbeHttpInfo(ip net.IP, _port uint16, dialTimeout time.Duration) (httpInfo *port.HttpInfo, isDailErr bool) { if httpClient == nil { httpClient = newHttpClient(dialTimeout) } var err error var rewriteUrl string var body []byte var resp *http.Response var schemes []string if util.IsUint16InList(_port, httpsTopPort) { //检测请求的_port是否在httpsTopPort列表中 schemes = []string{"https", "http"} }else { schemes = []string["http", "https"} } for _, scheme := range schemes { //循环使用https和http去请求 var rewriteNum int url2 := fmt.Sprintf("%s://%s:%d/", scheme, ip.String(), _port) goReq: resp, body, err = getReq(url2) //获取响应包和响应body if err != nil { if strings.HasSuffix(err.Error(), ioTimeoutStr) || strings.Contains(err.Error(), regusedStr) { return nil, true } continue } if resp != nil { if resp.ContentLength == -1 { resp.ContentLength = int64(len(body)) } //先在响应头中获取重定向url rewriteUrl2, _ := resp.Location() //重新获取url,提取响应包头中的location参数值 if rewriteUrl2 != nil { rewriteUrl = rewriteUrl2.String() }else { rewriteUrl = "" } //其次在body中获取重定向url location := GetLocation(body) //在title.go中,用于获取body中的location 的url,即重定向url if rewriteUrl == "" && location != "" { rewriteUrl = location } if location != "" && rewriteNum < 3 { if !strings.HasPrefix(location, "http") { //判断location前缀中是否http开头,如果不是,则进入下一个if if strings.HasPrefix(location, "/") { resp.Request.URL.Path = location }else { resp.Request.URL.Path = resp.Request.URL.Path[:strings.LastIndex(resp.Request.URL.Path, "/")+1] + location//将 HTTP 请求的 URL 路径修改为原路径中最后一个斜杠之前的部分,再添加上 location 变量的值。这样,新的路径将以最后一个斜杠结尾,然后连接上 location 的值。 } location = resp.Request.URL.String() } url2 = location rewriteNum++ goto goReq } httpInfo = new(port.HttpInfo) //在port.go文件中,为HttpInfo结构体 httpInfo.Url = resp.Request.URL.String() httpInfo.StatusCode = resp.StatusCode httpInfo.ContentLen = int(resp.ContentLength) httpInfo.Location = rewriteUrl httpInfo.Server = resp.Header.Get("Server") httpInfo.Title = ExtracTitle(body) //title.go文件中,获取响应包中的title if resp.TLS != nil && len(resp.TLS.PeerCertificates) > 0 { //如果是https,获取tls握手证书的信息 httpInfo.TlsCN = resp.TLS.PeerCertificates[0].Subject.CommonName //证书主题部分的通用名称信息 httpInfo.TlsDNS = resp.TLS.PeerCertificates[0].DNSNames //证书中包含的DNS名称列表 } //指纹信息 err = webfinger.ParseWebFingerData(webfinger.DefFingerData) if err == nil { resp.Body = io.NopCloser(bytes.NewReader(body)) httpInfo.Fingers = webfinger.WebFingerIdent(resp) //j检测识别响应头和响应body识别指纹 //favicon fau := webfinger.FindFaviconUrl(string(body)) //获取响应body中的favionUrl if fau != ""{ if !strings.HasPrefix(fau, "http") { fau = resp.Request.URL.String() + fau } _, body2, err2 := getReq(fau) //获取请求favicon的响应包信息 if err2 == nil && len(body2) != 0{ httpInfo.Fingers = append(httpInfo.Fingers, webfinger.WebFingerIdentByFavicon(body2)...)//检测favicon指纹 } } } if resp.StatusCode != 400 { break } } } return httpInfo, false }</code></pre> <p></p> </blockquote> <ul> <li><span style="color:#956fe7;">func getReq(url2 string) (resp *http.Response, body []byte, err error)</span></li> </ul> <blockquote> <ul> <li>构造一个 HTTP GET 请求,设置请求头,然后使用 <code>httpClient</code> 执行请求。</li> <li>从响应中读取内容,如果是文本类型则进行编码解析(尝试将非 UTF-8 编码的文本解析为 UTF-8)。</li> </ul> <pre><code class="language-Go">func getReq (url2 string) (resp *http.Response, body []byte, err error) { req, err := http.NewRequest(http.MethodGet, url2, http.Nobody) //创建get请求 if err != nil { return } req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36") req.Header.Set("Accept-Encoding", "gzip, deflate") req.Close = true //关闭keepalive resp, err = httpClient.Do(req) if err != nil { return } if resp.Body != http.NoBody && resp.Body != nil { //如果body不为空且不为http.NoBody,则提取body body, _ = getBody(resp) //在http.go文件中,主要为识别body的编码,读取body数据 if contentTypes, _ := resp.Header["Content-Type"]; len(contentTypes) > 0 { if strings.Contains(contentTypes[0], "text") {//如果响应类型是文本类型,则对响应体进行解码,将非utf-8编码的文本解析为utf-8 _body, err2 := DecodeData(body, resp.Header) if err2 == nil { body = _body } resp.Body = io.NopCloser(bytes.NewReader(body)) } } } return }</code></pre> <p></p> </blockquote> <p>ps:这些函数主要是尝试使用 HTTP 和 HTTPS 进行请求,根据响应提取 HTTP 服务的相关信息,包括标题、服务器信息、TLS 证书信息以及其他可能的指纹信息,为后续服务识别和指纹探测提供数据支持。</p> <h2 style="background-color:transparent;">四、小结</h2> <p>这部分的功能主要围绕在对http和https指纹的识别,通过网页titile和icon的hash进行指纹判断;其次是针对端口进行服务识别,主要匹配规则库在“fingerprint-webfinger-finger.json” 文件和“core-port-fingerprint-rules.go”文件中。</p> <p></p> </div> </div> </div> </div> </div> <!--PC和WAP自适应版--> <div id="SOHUCS" sid="1736114035088220160"></div> <script type="text/javascript" src="/views/front/js/chanyan.js"></script> <!-- 文章页-底部 动态广告位 --> <div class="youdao-fixed-ad" id="detail_ad_bottom"></div> </div> <div class="col-md-3"> <div class="row" id="ad"> <!-- 文章页-右侧1 动态广告位 --> <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_1"> </div> </div> <!-- 文章页-右侧2 动态广告位 --> <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_2"></div> </div> <!-- 文章页-右侧3 动态广告位 --> <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_3"></div> </div> </div> </div> </div> </div> </div> <div class="container"> <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(golang,开发语言,后端)</h4> <div id="paradigm-article-related"> <div class="recommend-post mb30"> <ul class="widget-links"> <li><a href="/article/1950194728943284224.htm" title="java实习生40多天有感" target="_blank">java实习生40多天有感</a> <span class="text-muted">别拿爱情当饭吃</span> <div>从5月15日开始,我开始第一步步入社会,我今年大三,在一家上市互联网公司做一名实习生,主要做java后端开发。开始的时候,觉得公司的环境挺不错的,不过因为公司在CBD,所以隔壁的午饭和晚饭都要20+RMB,而且还吃不饱,这让我感觉挺郁闷的。一到下午,我就会犯困(因为饿)。因此,我又不得不买一些干粮在公司屯着。关于技术,有一个比较大的项目在需求调研当中,我们做实习生,就是辅助项目经理,测试功能,并且</div> </li> <li><a href="/article/1950190146074767360.htm" title="大数据技术笔记—spring入门" target="_blank">大数据技术笔记—spring入门</a> <span class="text-muted">卿卿老祖</span> <div>篇一spring介绍spring.io官网快速开始Aop面向切面编程,可以任何位置,并且可以细致到方法上连接框架与框架Spring就是IOCAOP思想有效的组织中间层对象一般都是切入service层spring组成前后端分离已学方式,前后台未分离:Spring的远程通信:明日更新创建第一个spring项目来源:科多大数据</div> </li> <li><a href="/article/1950179866523529216.htm" title="大学社团管理系统(11831)" target="_blank">大学社团管理系统(11831)</a> <span class="text-muted">codercode2022</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/echarts/1.htm">echarts</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/cloud/1.htm">cloud</a><a class="tag" taget="_blank" href="/search/sentinel/1.htm">sentinel</a><a class="tag" taget="_blank" href="/search/java-rocketmq/1.htm">java-rocketmq</a> <div>有需要的同学,源代码和配套文档领取,加文章最下方的名片哦一、项目演示项目演示视频二、资料介绍完整源代码(前后端源代码+SQL脚本)配套文档(LW+PPT+开题报告)远程调试控屏包运行三、技术介绍Java语言SSM框架SpringBoot框架Vue框架JSP页面Mysql数据库IDEA/Eclipse开发四、项目截图有需要的同学,源代码和配套文档领取,加文章最下方的名片哦!</div> </li> <li><a href="/article/1950169524384886784.htm" title="【Java Web实战】从零到一打造企业级网上购书网站系统 | 完整开发实录(三)" target="_blank">【Java Web实战】从零到一打造企业级网上购书网站系统 | 完整开发实录(三)</a> <span class="text-muted">笙囧同学</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/1.htm">状态模式</a> <div>核心功能设计用户管理系统用户管理是整个系统的基础,我设计了完整的用户生命周期管理:用户注册流程验证失败验证通过验证失败验证通过用户名已存在用户名可用失败成功用户访问注册页面填写注册信息前端表单验证显示错误提示提交到后端后端数据验证返回错误信息用户名唯一性检查提示用户名重复密码加密处理保存用户信息保存成功?显示系统错误注册成功跳转登录页面登录认证机制深度解析我实现了一套企业级的多层次安全认证机制:认</div> </li> <li><a href="/article/1950169525244719104.htm" title="从零到一:基于差分隐私决策树的客户购买预测系统实战开发" target="_blank">从零到一:基于差分隐私决策树的客户购买预测系统实战开发</a> <span class="text-muted">笙囧同学</span> <a class="tag" taget="_blank" href="/search/%E5%86%B3%E7%AD%96%E6%A0%91/1.htm">决策树</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/1.htm">机器学习</a> <div>作者简介:笙囧同学,中科院计算机大模型方向硕士,全栈开发爱好者联系方式:3251736703@qq.com各大平台账号:笙囧同学座右铭:偷懒是人生进步的阶梯文章导航快速导航前言-项目背景与价值项目概览-系统架构与功能技术深度解析-核心算法原理️系统实现详解-工程实践细节性能评估与分析-实验结果分析Web系统开发-前后端开发部署与运维-DevOps实践完整复现指南-手把手教程️实践案例与故障排除-问</div> </li> <li><a href="/article/1950169526440095744.htm" title="从零到一:打造基于GigaChat AI的艺术创作平台 | 笙囧同学的全栈开发实战" target="_blank">从零到一:打造基于GigaChat AI的艺术创作平台 | 笙囧同学的全栈开发实战</a> <span class="text-muted"></span> <div>作者简介:笙囧同学,中科院计算机大模型方向硕士,全栈开发爱好者联系方式:3251736703@qq.com各大平台账号:笙囧同学座右铭:偷懒是人生进步的阶梯前言在AI技术飞速发展的今天,如何将前沿的大模型技术与实际应用相结合,一直是我们开发者关注的焦点。今天,笙囧同学将带大家从零开始,构建一个基于GigaChatAI的艺术创作平台,实现React前端+Django后端的完整全栈解决方案。这不仅仅是</div> </li> <li><a href="/article/1950160572926455808.htm" title="基于STM32设计的LCD指针式电子钟与日历项目" target="_blank">基于STM32设计的LCD指针式电子钟与日历项目</a> <span class="text-muted">鱼弦</span> <a class="tag" taget="_blank" href="/search/%E5%8D%95%E7%89%87%E6%9C%BA%E7%B3%BB%E7%BB%9F%E5%90%88%E9%9B%86/1.htm">单片机系统合集</a><a class="tag" taget="_blank" href="/search/stm32/1.htm">stm32</a><a class="tag" taget="_blank" href="/search/%E5%B5%8C%E5%85%A5%E5%BC%8F%E7%A1%AC%E4%BB%B6/1.htm">嵌入式硬件</a><a class="tag" taget="_blank" href="/search/%E5%8D%95%E7%89%87%E6%9C%BA/1.htm">单片机</a> <div>鱼弦:公众号【红尘灯塔】,CSDN博客专家、内容合伙人、新星导师、全栈领域优质创作者、51CTO(Top红人+专家博主)、github开源爱好者(go-zero源码二次开发、游戏后端架构https://github.com/Peakchen)基于STM32设计的LCD指针式电子钟与日历项目1.介绍基于STM32设计的LCD指针式电子钟与日历项目是一款利用STM32微控制器、LCD显示屏和指针机构实</div> </li> <li><a href="/article/1950131321980383232.htm" title="深入了解 Kubernetes(k8s):从概念到实践" target="_blank">深入了解 Kubernetes(k8s):从概念到实践</a> <span class="text-muted"></span> <div>目录一、k8s核心概念二、k8s的优势三、k8s架构组件控制平面组件节点组件四、k8s+docker运行前后端分离项目的例子1.准备前端项目2.准备后端项目3.创建k8s部署配置文件4.部署应用到k8s集群在当今云计算和容器化技术飞速发展的时代,Kubernetes(简称k8s)已成为容器编排领域的事实标准。无论是互联网巨头、传统企业还是初创公司,都在广泛采用k8s来管理和部署容器化应用。本文将带</div> </li> <li><a href="/article/1950110020356075520.htm" title="JAVA后端开发——用 Spring Boot 实现定时任务" target="_blank">JAVA后端开发——用 Spring Boot 实现定时任务</a> <span class="text-muted">1candobetter</span> <a class="tag" taget="_blank" href="/search/JAVA%E5%BC%80%E5%8F%91/1.htm">JAVA开发</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>在后端开发中,执行定时任务是一个极其常见的需求,无论是每日的数据报表生成、定时的缓存清理,还是自动化同步第三方数据。借助SpringBoot内置的强大功能,我们只需几个简单的注解,就能实现稳定、可靠且极易维护的定时任务。第一步:开启定时任务的总开关(@EnableScheduling)我们首先要告诉SpringBoot:“嘿,我准备在这个项目里使用定时任务功能了,请帮我把相关的组件都准备好!”这个</div> </li> <li><a href="/article/1950100058422702080.htm" title="从0到1学PHP(一):PHP 基础入门:开启后端开发之旅" target="_blank">从0到1学PHP(一):PHP 基础入门:开启后端开发之旅</a> <span class="text-muted"></span> <div>目录一、PHP简介与发展历程1.1PHP定义与特点1.2在后端开发中的地位1.3发展阶段及重要版本更新二、PHP开发环境搭建2.1Windows系统下搭建步骤2.2Mac系统下搭建方法及常用工具2.3适合初学者的集成开发环境三、第一个PHP程序3.1编写"HelloWorld"程序3.2程序基本结构和执行过程3.3PHP代码的嵌入方式(在HTML中)一、PHP简介与发展历程1.1PHP定义与特点P</div> </li> <li><a href="/article/1950094764498022400.htm" title="Coze Studio 架构拆解:AI Agent 开发平台项目结构全分析" target="_blank">Coze Studio 架构拆解:AI Agent 开发平台项目结构全分析</a> <span class="text-muted">代码简单说</span> <a class="tag" taget="_blank" href="/search/2025%E5%BC%80%E5%8F%91%E5%BF%85%E5%A4%87%28%E9%99%90%E6%97%B6%E7%89%B9%E6%83%A0%29/1.htm">2025开发必备(限时特惠)</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/Coze/1.htm">Coze</a><a class="tag" taget="_blank" href="/search/Studio/1.htm">Studio</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a><a class="tag" taget="_blank" href="/search/AI/1.htm">AI</a><a class="tag" taget="_blank" href="/search/Agent/1.htm">Agent</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E5%B9%B3%E5%8F%B0/1.htm">开发平台</a><a class="tag" taget="_blank" href="/search/%E5%85%A8%E6%A0%88/1.htm">全栈</a><a class="tag" taget="_blank" href="/search/AI/1.htm">AI</a><a class="tag" taget="_blank" href="/search/%E5%B7%A5%E7%A8%8B%E5%8C%96/1.htm">工程化</a><a class="tag" taget="_blank" href="/search/%E5%9B%BE%E8%A7%A3%E6%9E%B6%E6%9E%84/1.htm">图解架构</a> <div>CozeStudio架构拆解:AIAgent开发平台项目结构全分析标签:CozeStudio项目架构、领域驱动设计DDD、全栈开发规范、Hertz框架、前后端协作、云原生容器、前端测试、IDL接口设计、微服务解耦、AI开发平台源码分析在最近研究AIAgent开发平台的过程中,我深入分析了刚刚开源的CozeStudio项目。这套系统是国内少有的开源全栈AI工程化项目,代码整洁、架构先进,特别是它基于</div> </li> <li><a href="/article/1950094638102671360.htm" title="rabbitmq java 乱码,透彻分析和解决一切javaWeb项目乱码问题" target="_blank">rabbitmq java 乱码,透彻分析和解决一切javaWeb项目乱码问题</a> <span class="text-muted"></span> <div>前言乱码是我们在程序开发中经常碰到且让人头疼的一件事,尤其是我们在做javaweb开发,如果我们没有清楚乱码产生的原理,碰到乱码问题了就容易摸不着头脑,无从下手。乱码主要出现在两部分,如下:第一,浏览器通过表单提交到后台,如果表单内容有中文,那么后台收到的数据可能会出现乱码。第二,后端服务器需要返回给浏览器数据,如果数据中带有中文,那么浏览器上可能会显示乱码。接下来我们逐一分析乱码产生的原因,以及</div> </li> <li><a href="/article/1950089974804180992.htm" title="9、Docker Compose 实战" target="_blank">9、Docker Compose 实战</a> <span class="text-muted">小醉你真好</span> <a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/%E9%83%A8%E7%BD%B2%E4%B8%8D%E6%B1%82%E4%BA%BA/1.htm">部署不求人</a><a class="tag" taget="_blank" href="/search/docker/1.htm">docker</a><a class="tag" taget="_blank" href="/search/%E5%AE%B9%E5%99%A8/1.htm">容器</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a> <div>DockerCompose实战教程(含完整Nginx案例+配置项详解)适合读者:开发者、后端工程师、运维工程师、初学者环境要求:CentOS9+Docker已安装教程亮点:实战驱动、配置项详解、挂载说明、可直接复制使用标签:#Docker#DockerCompose#运维实战#Nginx部署一、什么是DockerCompose?DockerCompose是Docker官方推出的多容器应用编排工具,</div> </li> <li><a href="/article/1950076868195577856.htm" title="Hystrix停更了,Sentinel能接班吗?熔断降级技术深度对比!" target="_blank">Hystrix停更了,Sentinel能接班吗?熔断降级技术深度对比!</a> <span class="text-muted">我爱娃哈哈</span> <a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F%E6%8A%80%E6%9C%AF%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E6%88%98/1.htm">分布式技术原理与实战</a><a class="tag" taget="_blank" href="/search/hystrix/1.htm">hystrix</a><a class="tag" taget="_blank" href="/search/sentinel/1.htm">sentinel</a> <div>Hystrix停更了,Sentinel能接班吗?熔断降级技术深度对比!一、开场白:熔断降级,真能救活你的系统?还记得第一次遇到服务雪崩,老板一句话:"你熔断降级做了吗?"我一脸懵:"熔断降级?不就是Hystrix吗?"结果一查,Hystrix已经停更了,Sentinel成了新宠!今天咱们就聊聊,Hystrix到底是怎么工作的?Sentinel又有什么优势?一线后端工程师的深度技术对比!二、熔断降级</div> </li> <li><a href="/article/1950052911442620416.htm" title="最详细!教你学习haproxy七层代理" target="_blank">最详细!教你学习haproxy七层代理</a> <span class="text-muted">969库库库</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a> <div>一、工作原理(1)包括监听端口:HAProxy会在指定的端口上监听客户端的请求。例如,它可以监听常见的HTTP和HTTPS端口,等待客户端连接。请求接收:当客户端发起请求时,HAProxy接收到请求。它会解析请求的内容,包括请求的方法(如GET、POST等)、目标URL等。负载均衡决策:根据预先配置的负载均衡策略,决定将请求转发到后端的哪个服务器。常见的负载均衡算法有轮询、加权轮询、最少连接等。比</div> </li> <li><a href="/article/1950049760807284736.htm" title="尚庭公寓-学习跟敲笔记(二)" target="_blank">尚庭公寓-学习跟敲笔记(二)</a> <span class="text-muted">wenbinglin66</span> <a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>管理端后端开发-租赁管理模块1.看房预约管理1.1根据ID更新预约状态在ViewAppointmenController中增加内容@Operation(summary="根据id更新预约状态")@PostMapping("updateStatusById")publicResultupdateStatusById(@RequestParamLongid,@RequestParamAppointme</div> </li> <li><a href="/article/1950035646378733568.htm" title="深入剖析Nginx" target="_blank">深入剖析Nginx</a> <span class="text-muted">书火网_firebook</span> <div>想邀看书之《深入剖析Nginx》一个不会点运维的后端程序员,不是个合格的码农传送门:https://fire100.top/detail?rId=155少年辛苦终身事,莫向光阴惰寸功!</div> </li> <li><a href="/article/1950001596754620416.htm" title="基于SpringBoot生鲜水果商城管理系统" target="_blank">基于SpringBoot生鲜水果商城管理系统</a> <span class="text-muted">专业毕设vx dahusheji234</span> <a class="tag" taget="_blank" href="/search/JSP%E6%AF%95%E8%AE%BE/1.htm">JSP毕设</a><a class="tag" taget="_blank" href="/search/%E6%BA%90%E7%A0%81%E5%88%86%E4%BA%AB/1.htm">源码分享</a><a class="tag" taget="_blank" href="/search/asp.net/1.htm">asp.net</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a> <div>生鲜水果商城的实现主要包括:用户模块,后台管理模块其中用户模块包含有:首页商品显示用户登录注册,如果没有登录先提醒用户"请先登录.…,然后跳到登录界面,登录成功后再跳回到原来界面如果已经登录,加入购物车前,先判断该商品购物车中是否已经存在,如果存在则直接加数量,如果不存在则添加次商品信息到购物车。3、订单管理:用户可以根据自己需求购买商品,用户可以查看自己的订单信息,填写收货地址后端管理模块包含有</div> </li> <li><a href="/article/1949974347041009664.htm" title="毕业设计-宿舍管理系统" target="_blank">毕业设计-宿舍管理系统</a> <span class="text-muted">拜托了学长</span> <div>前言本期项目是宿舍管理系统,主要包括数据监控大盘、宿舍楼管理、宿舍管理、宿舍成员管理、借用管理、卫生管理、缴费管理、保修管理、日志管理、用户管理、角色管理以及各个模块的导出功能。以企业级的开发标准来完成整个前后端代码,无论是用来作为毕业设计还是拿来学习,相信对初学者都会有很大帮助。(想要源码和视频教程的同学私信我~~~)工程架构应用分层image-20201226111957265上面的分层架构摘</div> </li> <li><a href="/article/1949963001104756736.htm" title="Python,C++,go语言开发社会犯罪人群回归社会跟踪与辅助管理APP" target="_blank">Python,C++,go语言开发社会犯罪人群回归社会跟踪与辅助管理APP</a> <span class="text-muted">Geeker-2025</span> <a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/golang/1.htm">golang</a> <div>开发一款用于**社会犯罪人群回归社会跟踪与辅助管理**的App,结合Python、C++和Go语言的优势,可以实现高效的数据处理、实时的跟踪监控以及用户友好的前端界面。以下是一个详细的开发方案,涵盖技术选型、功能模块、开发步骤等内容。##技术选型###后端(Python+Go)-**编程语言**:-**Python**:用于数据处理、机器学习(如风险评估、行为预测)、脚本编写等。-**Go**:用</div> </li> <li><a href="/article/1949963000672743424.htm" title="Python, C ++开发冷冻食品供应链管理app" target="_blank">Python, C ++开发冷冻食品供应链管理app</a> <span class="text-muted">Geeker-2025</span> <a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a> <div>开发一款用于**冷冻食品供应链管理**的App,结合Python和C++的优势,可以实现高效的后端数据处理、实时的供应链监控以及用户友好的前端界面。以下是一个详细的开发方案,涵盖技术选型、功能模块、开发步骤等内容。##技术选型###后端(Python)-**编程语言**:Python-**Web框架**:Django或Flask-**数据库**:PostgreSQL或MySQL-**实时通信**:</div> </li> <li><a href="/article/1949926944988524544.htm" title="壹脉销客AI电子名片源码核心架构" target="_blank">壹脉销客AI电子名片源码核心架构</a> <span class="text-muted"></span> <div>为什么选择源码部署AI电子名片?在数字化转型浪潮中,越来越多的企业意识到拥有自主可控的电子名片系统的重要性。源码部署相比SaaS服务具有三大核心优势:数据完全自主-客户信息存储在企业自有服务器深度定制自由-可根据业务需求二次开发长期成本优化-一次部署永久使用壹脉销客AI电子名片源码核心架构壹脉销客提供的企业级电子名片解决方案采用前后端分离架构:前端技术栈(小程序端)javascript//小程序a</div> </li> <li><a href="/article/1949917489789988864.htm" title="企业级网站源码:一键优化与全站静态生成" target="_blank">企业级网站源码:一键优化与全站静态生成</a> <span class="text-muted">DIY飞跃计划</span> <div>本文还有配套的精品资源,点击获取简介:本文介绍了一个具有商业价值的企业级网站源码包,包含了完整的前后端设计和逻辑处理。源码具备后台一键优化功能,可通过简单操作提升网站性能,如数据库优化、资源压缩和缓存管理。源码支持生成全站静态页面,以提高加载速度和SEO表现。同时提供了详细的安装和使用文档,以及相关的调试和部署工具,使得开发者能够快速搭建和维护定制化的网站解决方案。1.企业级网站源码的商业价值与应</div> </li> <li><a href="/article/1949915473982320640.htm" title="HAProxy 负载均衡指南" target="_blank">HAProxy 负载均衡指南</a> <span class="text-muted">心上之秋</span> <a class="tag" taget="_blank" href="/search/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/1.htm">负载均衡</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a> <div>一、HAProxy简介HAProxy(HighAvailabilityProxy)是一款高性能、开源的负载均衡器和代理服务器。它以其高并发处理能力、灵活的配置选项和强大的功能而闻名,广泛应用于各种Web服务场景,如:负载均衡:将流量分配到多个后端服务器,提高系统可用性和性能。反向代理:隐藏真实服务器,提供安全防护、缓存内容等功能。SSL/TLS终止:处理HTTPS请求,提高网站安全性。Web性能优</div> </li> <li><a href="/article/1949899710185664512.htm" title="终面倒计时10分钟:候选人用`memory_profiler`定位Python内存泄漏" target="_blank">终面倒计时10分钟:候选人用`memory_profiler`定位Python内存泄漏</a> <span class="text-muted">itAred</span> <a class="tag" taget="_blank" href="/search/Python%E9%9D%A2%E8%AF%95%E5%9C%BA%E6%99%AF%E9%A2%98/1.htm">Python面试场景题</a><a class="tag" taget="_blank" href="/search/Python/1.htm">Python</a><a class="tag" taget="_blank" href="/search/Memory/1.htm">Memory</a><a class="tag" taget="_blank" href="/search/Profiling/1.htm">Profiling</a><a class="tag" taget="_blank" href="/search/Interview/1.htm">Interview</a><a class="tag" taget="_blank" href="/search/Debugging/1.htm">Debugging</a> <div>场景设定:终面倒计时10分钟面试官:小兰,欢迎来到终面环节。在你前面的候选人已经展示了他们的项目经历和代码能力,但今天的终面,我们想考察你解决实际问题的能力。现在,假设你是一名资深后端工程师,负责维护一个高并发的在线服务。最近,生产环境的服务器内存占用持续升高,甚至出现了服务频繁挂掉的问题。我们需要你快速定位并解决这个问题。在接下来的10分钟内,我会给你一段简化的代码示例,并提供一个内存泄漏的场景</div> </li> <li><a href="/article/1949897298091765760.htm" title="Golang不能发送udp广播" target="_blank">Golang不能发送udp广播</a> <span class="text-muted">大地缸</span> <div>title:"Golang不能发送udp广播"date:2021-01-29T20:55:16+08:00draft:truetags:['go','udp']author:"dadigang"author_cn:"大地缸"personal:"http://www.real007.cn"关于作者http://www.real007.cn/aboutgo不能发送udp广播,这算不算是一个bug?ne</div> </li> <li><a href="/article/1949892891295936512.htm" title="Spring Gateway转发websocket原理" target="_blank">Spring Gateway转发websocket原理</a> <span class="text-muted">李昂的数字之旅</span> <a class="tag" taget="_blank" href="/search/SpringBoot/1.htm">SpringBoot</a><a class="tag" taget="_blank" href="/search/gateway/1.htm">gateway</a><a class="tag" taget="_blank" href="/search/websocket/1.htm">websocket</a><a class="tag" taget="_blank" href="/search/spring%E7%BD%91%E5%85%B3/1.htm">spring网关</a> <div>SpringCloudGateway简称SpringGateway,它可以转发请求到后端微服务。SpringGateway除了转发HTTP请求,也支持websocket请求。我们看下它是怎么实现的吧。配置支持websocket转发支持websocket转发,需要用到spring-cloud-starter-gateway,不要搞错成spring-cloud-starter-gateway-web。</div> </li> <li><a href="/article/1949892134148567040.htm" title="在vue项目中嵌入Python项目" target="_blank">在vue项目中嵌入Python项目</a> <span class="text-muted">钱亚锋</span> <a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/ecmascript/1.htm">ecmascript</a> <div>在Vue项目中嵌入Python项目在现代Web开发中,前后端分离的架构已成为一种流行趋势。前端使用现代化框架(如Vue.js)来构建用户界面,而后端则使用其他语言(如Python)来处理复杂的业务逻辑和数据库交互。将Python项目嵌入到Vue项目中,可以有效利用两种语言的优势,提升开发效率。本文将介绍如何在Vue项目中集成Python项目,并附带代码示例和可视化工具。流程概述在将Python项目</div> </li> <li><a href="/article/1949867673982660608.htm" title="Flink Checkpoint 状态后端详解:类型、特性对比及场景化选型指南" target="_blank">Flink Checkpoint 状态后端详解:类型、特性对比及场景化选型指南</a> <span class="text-muted"></span> <div>ApacheFlink提供了多种状态后端以支持Checkpoint机制下的状态持久化,确保在故障发生时能够快速恢复状态并实现Exactly-Once处理语义。以下是几种常见状态后端的详细介绍及其对比情况,以及不同场景下的选型建议:1.MemoryStateBackend(内存状态后端)描述:MemoryStateBackend将状态数据存储在TaskManager的JVM堆内存中,并在Checkp</div> </li> <li><a href="/article/1949854043165749248.htm" title="今日Github热门仓库推荐 第八期" target="_blank">今日Github热门仓库推荐 第八期</a> <span class="text-muted">桃白白大人</span> <a class="tag" taget="_blank" href="/search/Github%E7%83%AD%E9%97%A8%E9%A1%B9%E7%9B%AE%E6%8E%A8%E8%8D%90/1.htm">Github热门项目推荐</a><a class="tag" taget="_blank" href="/search/github/1.htm">github</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a> <div>今日Github热门仓库推荐2025-07-22如果让AI分别扮演后端开发人员和前端开发人员,然后看看他们分别对github每天的trending仓库感兴趣的有哪些,并且给出他感兴趣的理由,那会发生什么呢?本内容通过Python+AI生成,项目地址跳转后端开发人员推荐仓库名称:donnemartin/system-design-primer仓库推荐理由:作为后端开发工程师,系统设计是核心技能之一。</div> </li> <li><a href="/article/110.htm" title="强大的销售团队背后 竟然是大数据分析的身影" target="_blank">强大的销售团队背后 竟然是大数据分析的身影</a> <span class="text-muted">蓝儿唯美</span> <a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/1.htm">数据分析</a> <div>Mark Roberge是HubSpot的首席财务官,在招聘销售职位时使用了大量数据分析。但是科技并没有挤走直觉。 大家都知道数理学家实际上已经渗透到了各行各业。这些热衷数据的人们通过处理数据理解商业流程的各个方面,以重组弱点,增强优势。 Mark Roberge是美国HubSpot公司的首席财务官,HubSpot公司在构架集客营销现象方面出过一份力——因此他也是一位数理学家。他使用数据分析 </div> </li> <li><a href="/article/237.htm" title="Haproxy+Keepalived高可用双机单活" target="_blank">Haproxy+Keepalived高可用双机单活</a> <span class="text-muted">bylijinnan</span> <a class="tag" taget="_blank" href="/search/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/1.htm">负载均衡</a><a class="tag" taget="_blank" href="/search/keepalived/1.htm">keepalived</a><a class="tag" taget="_blank" href="/search/haproxy/1.htm">haproxy</a><a class="tag" taget="_blank" href="/search/%E9%AB%98%E5%8F%AF%E7%94%A8/1.htm">高可用</a> <div>我们的应用MyApp不支持集群,但要求双机单活(两台机器:master和slave): 1.正常情况下,只有master启动MyApp并提供服务 2.当master发生故障时,slave自动启动本机的MyApp,同时虚拟IP漂移至slave,保持对外提供服务的IP和端口不变 F5据说也能满足上面的需求,但F5的通常用法都是双机双活,单活的话还没研究过 服务器资源 10.7</div> </li> <li><a href="/article/364.htm" title="eclipse编辑器中文乱码问题解决" target="_blank">eclipse编辑器中文乱码问题解决</a> <span class="text-muted">0624chenhong</span> <a class="tag" taget="_blank" href="/search/eclipse%E4%B9%B1%E7%A0%81/1.htm">eclipse乱码</a> <div>使用Eclipse编辑文件经常出现中文乱码或者文件中有中文不能保存的问题,Eclipse提供了灵活的设置文件编码格式的选项,我们可以通过设置编码 格式解决乱码问题。在Eclipse可以从几个层面设置编码格式:Workspace、Project、Content Type、File 本文以Eclipse 3.3(英文)为例加以说明: 1. 设置Workspace的编码格式: Windows-&g</div> </li> <li><a href="/article/491.htm" title="基础篇--resources资源" target="_blank">基础篇--resources资源</a> <span class="text-muted">不懂事的小屁孩</span> <a class="tag" taget="_blank" href="/search/android/1.htm">android</a> <div>最近一直在做java开发,偶尔敲点android代码,突然发现有些基础给忘记了,今天用半天时间温顾一下resources的资源。 String.xml    字符串资源   涉及国际化问题  http://www.2cto.com/kf/201302/190394.html   string-array</div> </li> <li><a href="/article/618.htm" title="接上篇补上window平台自动上传证书文件的批处理问卷" target="_blank">接上篇补上window平台自动上传证书文件的批处理问卷</a> <span class="text-muted">酷的飞上天空</span> <a class="tag" taget="_blank" href="/search/window/1.htm">window</a> <div> @echo off : host=服务器证书域名或ip,需要和部署时服务器的域名或ip一致 ou=公司名称, o=公司名称 set host=localhost set ou=localhost set o=localhost set password=123456 set validity=3650 set salias=s</div> </li> <li><a href="/article/745.htm" title="企业物联网大潮涌动:如何做好准备?" target="_blank">企业物联网大潮涌动:如何做好准备?</a> <span class="text-muted">蓝儿唯美</span> <a class="tag" taget="_blank" href="/search/%E4%BC%81%E4%B8%9A/1.htm">企业</a> <div>物联网的可能性也许是无限的。要找出架构师可以做好准备的领域然后利用日益连接的世界。 尽管物联网(IoT)还很新,企业架构师现在也应该为一个连接更加紧密的未来做好计划,而不是跟上闸门被打开后的集成挑战。“问题不在于物联网正在进入哪些领域,而是哪些地方物联网没有在企业推进,” Gartner研究总监Mike Walker说。 Gartner预测到2020年物联网设备安装量将达260亿,这些设备在全</div> </li> <li><a href="/article/872.htm" title="spring学习——数据库(mybatis持久化框架配置)" target="_blank">spring学习——数据库(mybatis持久化框架配置)</a> <span class="text-muted">a-john</span> <a class="tag" taget="_blank" href="/search/mybatis/1.htm">mybatis</a> <div>Spring提供了一组数据访问框架,集成了多种数据访问技术。无论是JDBC,iBATIS(mybatis)还是Hibernate,Spring都能够帮助消除持久化代码中单调枯燥的数据访问逻辑。可以依赖Spring来处理底层的数据访问。 mybatis是一种Spring持久化框架,要使用mybatis,就要做好相应的配置: 1,配置数据源。有很多数据源可以选择,如:DBCP,JDBC,aliba</div> </li> <li><a href="/article/999.htm" title="Java静态代理、动态代理实例" target="_blank">Java静态代理、动态代理实例</a> <span class="text-muted">aijuans</span> <a class="tag" taget="_blank" href="/search/Java%E9%9D%99%E6%80%81%E4%BB%A3%E7%90%86/1.htm">Java静态代理</a> <div> 采用Java代理模式,代理类通过调用委托类对象的方法,来提供特定的服务。委托类需要实现一个业务接口,代理类返回委托类的实例接口对象。 按照代理类的创建时期,可以分为:静态代理和动态代理。 所谓静态代理: 指程序员创建好代理类,编译时直接生成代理类的字节码文件。 所谓动态代理: 在程序运行时,通过反射机制动态生成代理类。   一、静态代理类实例: 1、Serivce.ja</div> </li> <li><a href="/article/1126.htm" title="Struts1与Struts2的12点区别" target="_blank">Struts1与Struts2的12点区别</a> <span class="text-muted">asia007</span> <a class="tag" taget="_blank" href="/search/Struts1%E4%B8%8EStruts2/1.htm">Struts1与Struts2</a> <div>1) 在Action实现类方面的对比:Struts 1要求Action类继承一个抽象基类;Struts 1的一个具体问题是使用抽象类编程而不是接口。Struts 2 Action类可以实现一个Action接口,也可以实现其他接口,使可选和定制的服务成为可能。Struts 2提供一个ActionSupport基类去实现常用的接口。即使Action接口不是必须实现的,只有一个包含execute方法的P</div> </li> <li><a href="/article/1253.htm" title="初学者要多看看帮助文档 不要用js来写Jquery的代码" target="_blank">初学者要多看看帮助文档 不要用js来写Jquery的代码</a> <span class="text-muted">百合不是茶</span> <a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a><a class="tag" taget="_blank" href="/search/js/1.htm">js</a> <div>解析json数据的时候需要将解析的数据写到文本框中,  出现了用js来写Jquery代码的问题;   1, JQuery的赋值  有问题    代码如下: data.username 表示的是:  网易            $("#use</div> </li> <li><a href="/article/1380.htm" title="经理怎么和员工搞好关系和信任" target="_blank">经理怎么和员工搞好关系和信任</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/%E5%9B%A2%E9%98%9F/1.htm">团队</a><a class="tag" taget="_blank" href="/search/%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86/1.htm">项目管理</a><a class="tag" taget="_blank" href="/search/%E7%AE%A1%E7%90%86/1.htm">管理</a> <div>        产品经理应该有坚实的专业基础,这里的基础包括产品方向和产品策略的把握,包括设计,也包括对技术的理解和见识,对运营和市场的敏感,以及良好的沟通和协作能力。换言之,既然是产品经理,整个产品的方方面面都应该能摸得出门道。这也不懂那也不懂,如何让人信服?如何让自己懂?就是不断学习,不仅仅从书本中,更从平时和各种角色的沟通</div> </li> <li><a href="/article/1507.htm" title="如何为rich:tree不同类型节点设置右键菜单" target="_blank">如何为rich:tree不同类型节点设置右键菜单</a> <span class="text-muted">sunjing</span> <a class="tag" taget="_blank" href="/search/contextMenu/1.htm">contextMenu</a><a class="tag" taget="_blank" href="/search/tree/1.htm">tree</a><a class="tag" taget="_blank" href="/search/Richfaces/1.htm">Richfaces</a> <div>组合使用target和targetSelector就可以啦,如下: <rich:tree id="ruleTree" value="#{treeAction.ruleTree}" var="node" nodeType="#{node.type}" selectionChangeListener=&qu</div> </li> <li><a href="/article/1634.htm" title="【Redis二】Redis2.8.17搭建主从复制环境" target="_blank">【Redis二】Redis2.8.17搭建主从复制环境</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/redis/1.htm">redis</a> <div>开始使用Redis2.8.17 Redis第一篇在Redis2.4.5上搭建主从复制环境,对它的主从复制的工作机制,真正的惊呆了。不知道Redis2.8.17的主从复制机制是怎样的,Redis到了2.4.5这个版本,主从复制还做成那样,Impossible is nothing! 本篇把主从复制环境再搭一遍看看效果,这次在Unbuntu上用官方支持的版本。   Ubuntu上安装Red</div> </li> <li><a href="/article/1761.htm" title="JSONObject转换JSON--将Date转换为指定格式" target="_blank">JSONObject转换JSON--将Date转换为指定格式</a> <span class="text-muted">白糖_</span> <a class="tag" taget="_blank" href="/search/JSONObject/1.htm">JSONObject</a> <div>项目中,经常会用JSONObject插件将JavaBean或List<JavaBean>转换为JSON格式的字符串,而JavaBean的属性有时候会有java.util.Date这个类型的时间对象,这时JSONObject默认会将Date属性转换成这样的格式:   {"nanos":0,"time":-27076233600000,</div> </li> <li><a href="/article/1888.htm" title="JavaScript语言精粹读书笔记" target="_blank">JavaScript语言精粹读书笔记</a> <span class="text-muted">braveCS</span> <a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a> <div>【经典用法】:   //①定义新方法 Function .prototype.method=function(name, func){ this.prototype[name]=func; return this; } //②给Object增加一个create方法,这个方法创建一个使用原对</div> </li> <li><a href="/article/2015.htm" title="编程之美-找符合条件的整数 用字符串来表示大整数避免溢出" target="_blank">编程之美-找符合条件的整数 用字符串来表示大整数避免溢出</a> <span class="text-muted">bylijinnan</span> <a class="tag" taget="_blank" href="/search/%E7%BC%96%E7%A8%8B%E4%B9%8B%E7%BE%8E/1.htm">编程之美</a> <div> import java.util.LinkedList; public class FindInteger { /** * 编程之美 找符合条件的整数 用字符串来表示大整数避免溢出 * 题目:任意给定一个正整数N,求一个最小的正整数M(M>1),使得N*M的十进制表示形式里只含有1和0 * * 假设当前正在搜索由0,1组成的K位十进制数</div> </li> <li><a href="/article/2142.htm" title="读书笔记" target="_blank">读书笔记</a> <span class="text-muted">chengxuyuancsdn</span> <a class="tag" taget="_blank" href="/search/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/1.htm">读书笔记</a> <div>1、Struts访问资源 2、把静态参数传递给一个动作 3、<result>type属性 4、s:iterator、s:if c:forEach 5、StringBuilder和StringBuffer 6、spring配置拦截器 1、访问资源 (1)通过ServletActionContext对象和实现ServletContextAware,ServletReque</div> </li> <li><a href="/article/2269.htm" title="[通讯与电力]光网城市建设的一些问题" target="_blank">[通讯与电力]光网城市建设的一些问题</a> <span class="text-muted">comsci</span> <a class="tag" taget="_blank" href="/search/%E9%97%AE%E9%A2%98/1.htm">问题</a> <div>       信号防护的问题,前面已经说过了,这里要说光网交换机与市电保障的关系       我们过去用的ADSL线路,因为是电话线,在小区和街道电力中断的情况下,只要在家里用笔记本电脑+蓄电池,连接ADSL,同样可以上网........     </div> </li> <li><a href="/article/2396.htm" title="oracle 空间RESUMABLE" target="_blank">oracle 空间RESUMABLE</a> <span class="text-muted">daizj</span> <a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/%E7%A9%BA%E9%97%B4%E4%B8%8D%E8%B6%B3/1.htm">空间不足</a><a class="tag" taget="_blank" href="/search/RESUMABLE/1.htm">RESUMABLE</a><a class="tag" taget="_blank" href="/search/%E9%94%99%E8%AF%AF%E6%8C%82%E8%B5%B7/1.htm">错误挂起</a> <div>空间RESUMABLE操作  转 Oracle从9i开始引入这个功能,当出现空间不足等相关的错误时,Oracle可以不是马上返回错误信息,并回滚当前的操作,而是将操作挂起,直到挂起时间超过RESUMABLE TIMEOUT,或者空间不足的错误被解决。 这一篇简单介绍空间RESUMABLE的例子。 第一次碰到这个特性是在一次安装9i数据库的过程中,在利用D</div> </li> <li><a href="/article/2523.htm" title="重构第一次写的线程池" target="_blank">重构第一次写的线程池</a> <span class="text-muted">dieslrae</span> <a class="tag" taget="_blank" href="/search/%E7%BA%BF%E7%A8%8B%E6%B1%A0+python/1.htm">线程池 python</a> <div>最近没有什么学习欲望,修改之前的线程池的计划一直搁置,这几天比较闲,还是做了一次重构,由之前的2个类拆分为现在的4个类. 1、首先是工作线程类:TaskThread,此类为一个工作线程,用于完成一个工作任务,提供等待(wait),继续(proceed),绑定任务(bindTask)等方法 #!/usr/bin/env python # -*- coding:utf8 -*- </div> </li> <li><a href="/article/2650.htm" title="C语言学习六指针" target="_blank">C语言学习六指针</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/c/1.htm">c</a> <div>初识指针,简单示例程序: /* 指针就是地址,地址就是指针 地址就是内存单元的编号 指针变量是存放地址的变量 指针和指针变量是两个不同的概念 但是要注意: 通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样 */ # include <stdio.h> int main(void) { int * p; // p是变量的名字, int * </div> </li> <li><a href="/article/2777.htm" title="yii2 beforeSave afterSave beforeDelete" target="_blank">yii2 beforeSave afterSave beforeDelete</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/delete/1.htm">delete</a> <div>public function afterSave($insert, $changedAttributes) { parent::afterSave($insert, $changedAttributes); if($insert) { //这里是新增数据 } else { //这里是更新数据 } }  </div> </li> <li><a href="/article/2904.htm" title="timertask" target="_blank">timertask</a> <span class="text-muted">shuizhaosi888</span> <a class="tag" taget="_blank" href="/search/timertask/1.htm">timertask</a> <div>java.util.Timer timer = new java.util.Timer(true); // true 说明这个timer以daemon方式运行(优先级低, // 程序结束timer也自动结束),注意,javax.swing // 包中也有一个Timer类,如果import中用到swing包, // 要注意名字的冲突。 TimerTask task = new</div> </li> <li><a href="/article/3031.htm" title="Spring Security(13)——session管理" target="_blank">Spring Security(13)——session管理</a> <span class="text-muted">234390216</span> <a class="tag" taget="_blank" href="/search/session/1.htm">session</a><a class="tag" taget="_blank" href="/search/Spring+Security/1.htm">Spring Security</a><a class="tag" taget="_blank" href="/search/%E6%94%BB%E5%87%BB%E4%BF%9D%E6%8A%A4/1.htm">攻击保护</a><a class="tag" taget="_blank" href="/search/%E8%B6%85%E6%97%B6/1.htm">超时</a> <div>session管理 目录   1.1     检测session超时 1.2     concurrency-control 1.3     session 固定攻击保护         </div> </li> <li><a href="/article/3158.htm" title="公司项目NODEJS实践0.3[ mongo / session ...]" target="_blank">公司项目NODEJS实践0.3[ mongo / session ...]</a> <span class="text-muted">逐行分析JS源代码</span> <a class="tag" taget="_blank" href="/search/mongodb/1.htm">mongodb</a><a class="tag" taget="_blank" href="/search/session/1.htm">session</a><a class="tag" taget="_blank" href="/search/nodejs/1.htm">nodejs</a> <div>    http://www.upopen.cn   一、前言         书接上回,我们搭建了WEB服务端路由、模板等功能,完成了register 通过ajax与后端的通信,今天主要完成数据与mongodb的存取,实现注册 / 登录 /</div> </li> <li><a href="/article/3285.htm" title="pojo.vo.po.domain区别" target="_blank">pojo.vo.po.domain区别</a> <span class="text-muted">LiaoJuncai</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/VO/1.htm">VO</a><a class="tag" taget="_blank" href="/search/POJO/1.htm">POJO</a><a class="tag" taget="_blank" href="/search/javabean/1.htm">javabean</a><a class="tag" taget="_blank" href="/search/domain/1.htm">domain</a> <div>  POJO = "Plain Old Java Object",是MartinFowler等发明的一个术语,用来表示普通的Java对象,不是JavaBean, EntityBean 或者 SessionBean。POJO不但当任何特殊的角色,也不实现任何特殊的Java框架的接口如,EJB, JDBC等等。      即POJO是一个简单的普通的Java对象,它包含业务逻辑</div> </li> <li><a href="/article/3412.htm" title="Windows Error Code" target="_blank">Windows Error Code</a> <span class="text-muted">OhMyCC</span> <a class="tag" taget="_blank" href="/search/windows/1.htm">windows</a> <div>0 操作成功完成. 1 功能错误. 2 系统找不到指定的文件. 3 系统找不到指定的路径. 4 系统无法打开文件. 5 拒绝访问. 6 句柄无效. 7 存储控制块被损坏. 8 存储空间不足, 无法处理此命令. 9 存储控制块地址无效. 10 环境错误. 11 试图加载格式错误的程序. 12 访问码无效. 13 数据无效. 14 存储器不足, 无法完成此操作. 15 系</div> </li> <li><a href="/article/3539.htm" title="在storm集群环境下发布Topology" target="_blank">在storm集群环境下发布Topology</a> <span class="text-muted">roadrunners</span> <a class="tag" taget="_blank" href="/search/%E9%9B%86%E7%BE%A4/1.htm">集群</a><a class="tag" taget="_blank" href="/search/storm/1.htm">storm</a><a class="tag" taget="_blank" href="/search/topology/1.htm">topology</a><a class="tag" taget="_blank" href="/search/spout/1.htm">spout</a><a class="tag" taget="_blank" href="/search/bolt/1.htm">bolt</a> <div>storm的topology设计和开发就略过了。本章主要来说说如何在storm的集群环境中,通过storm的管理命令来发布和管理集群中的topology。   1、打包 打包插件是使用maven提供的maven-shade-plugin,详细见maven-shade-plugin。 <plugin> <groupId>org.apache.maven.</div> </li> <li><a href="/article/3666.htm" title="为什么不允许代码里出现“魔数”" target="_blank">为什么不允许代码里出现“魔数”</a> <span class="text-muted">tomcat_oracle</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>  在一个新项目中,我最先做的事情之一,就是建立使用诸如Checkstyle和Findbugs之类工具的准则。目的是制定一些代码规范,以及避免通过静态代码分析就能够检测到的bug。   迟早会有人给出案例说这样太离谱了。其中的一个案例是Checkstyle的魔数检查。它会对任何没有定义常量就使用的数字字面量给出警告,除了-1、0、1和2。   很多开发者在这个检查方面都有问题,这可以从结果</div> </li> <li><a href="/article/3793.htm" title="zoj 3511 Cake Robbery(线段树)" target="_blank">zoj 3511 Cake Robbery(线段树)</a> <span class="text-muted">阿尔萨斯</span> <a class="tag" taget="_blank" href="/search/%E7%BA%BF%E6%AE%B5%E6%A0%91/1.htm">线段树</a> <div> 题目链接:zoj 3511 Cake Robbery 题目大意:就是有一个N边形的蛋糕,切M刀,从中挑选一块边数最多的,保证没有两条边重叠。 解题思路:有多少个顶点即为有多少条边,所以直接按照切刀切掉点的个数排序,然后用线段树维护剩下的还有哪些点。 #include <cstdio> #include <cstring> #include <vector&</div> </li> </ul> </div> </div> </div> <div> <div class="container"> <div class="indexes"> <strong>按字母分类:</strong> <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a> </div> </div> </div> <footer id="footer" class="mb30 mt30"> <div class="container"> <div class="footBglm"> <a target="_blank" href="/">首页</a> - <a target="_blank" href="/custom/about.htm">关于我们</a> - <a target="_blank" href="/search/Java/1.htm">站内搜索</a> - <a target="_blank" href="/sitemap.txt">Sitemap</a> - <a target="_blank" href="/custom/delete.htm">侵权投诉</a> </div> <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved. <!-- <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>--> </div> </div> </footer> <!-- 代码高亮 --> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script> <link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/> <script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script> </body> </html>