``feat(ip): 新增IP定位查询功能,支持通过API和网页解析获取归属地信息``
This commit is contained in:
@@ -3,19 +3,34 @@ package qqwry
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
"github.com/ipipdotnet/ipdb-go"
|
"github.com/ipipdotnet/ipdb-go"
|
||||||
"golang.org/x/text/encoding/simplifiedchinese"
|
"golang.org/x/text/encoding/simplifiedchinese"
|
||||||
"golang.org/x/text/transform"
|
"golang.org/x/text/transform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed qqwry.ipdb
|
||||||
|
var qqwry []byte
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
LoadData(qqwry)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
data []byte
|
data []byte
|
||||||
dataLen uint32
|
dataLen uint32
|
||||||
@@ -73,6 +88,103 @@ func QueryIP(ip string) (location *Location, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIpAddress 获取 IP 归属地和运营商
|
||||||
|
func GetIpAddress(ip string) (string, error) {
|
||||||
|
html, err := doGet("https://www.ipshudi.com/" + ip + "/")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
div := getAddress(html, `<td>\n<span>.*</span>\n`)
|
||||||
|
div1 := getAddress(html, `<td class="th">运营商</td><td><span>.*</span>`)
|
||||||
|
|
||||||
|
location := "未知归属地"
|
||||||
|
if div != "" {
|
||||||
|
location = strings.Split(div, "<span>")[1]
|
||||||
|
location = strings.Split(location, "</span>")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
isp := ""
|
||||||
|
if div1 != "" {
|
||||||
|
isp = strings.Split(div1, "<span>")[1]
|
||||||
|
isp = strings.Split(isp, "</span>")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return location + " " + isp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义响应数据结构(与 API 返回字段对应)
|
||||||
|
type IPLocationResponse struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
IPNumber string `json:"ip_number"`
|
||||||
|
IPVersion int `json:"ip_version"`
|
||||||
|
CountryName string `json:"country_name"`
|
||||||
|
CountryCode2 string `json:"country_code2"`
|
||||||
|
ISP string `json:"isp"`
|
||||||
|
ResponseCode string `json:"response_code"`
|
||||||
|
ResponseMessage string `json:"response_message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用 IP 定位 API
|
||||||
|
func LookupIP(ip string, format string) (*IPLocationResponse, error) {
|
||||||
|
// 构建请求 URL
|
||||||
|
baseURL := "https://api.iplocation.net/"
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("ip", ip)
|
||||||
|
if format != "" {
|
||||||
|
params.Add("format", format)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullURL := baseURL + "?" + params.Encode()
|
||||||
|
|
||||||
|
// 发送 GET 请求
|
||||||
|
resp, err := http.Get(fullURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("请求失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 读取响应体
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 JSON 响应(默认格式为 json)
|
||||||
|
var result IPLocationResponse
|
||||||
|
if err := json.Unmarshal(body, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析响应失败: %v (响应内容: %s)", err, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 API 响应状态
|
||||||
|
if result.ResponseCode != "200" {
|
||||||
|
return nil, fmt.Errorf("API 调用失败: %s (%s)", result.ResponseMessage, result.ResponseCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doGet 发送 HTTP GET 请求,返回响应内容
|
||||||
|
func doGet(url string) ([]byte, error) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAddress 使用正则表达式从 HTML 中提取内容
|
||||||
|
func getAddress(html []byte, pattern string) string {
|
||||||
|
re := regexp.MustCompile(pattern)
|
||||||
|
return re.FindString(string(html))
|
||||||
|
}
|
||||||
|
|
||||||
// 判断IP是否为私有地址(包括IPv4和IPv6)
|
// 判断IP是否为私有地址(包括IPv4和IPv6)
|
||||||
func isPrivateIP(ipStr string) (bool, error) {
|
func isPrivateIP(ipStr string) (bool, error) {
|
||||||
ip := net.ParseIP(ipStr)
|
ip := net.ParseIP(ipStr)
|
||||||
@@ -132,10 +244,14 @@ func QueryIPDat(ipv4 string) (location *Location, err error) {
|
|||||||
var offset uint32 = 0
|
var offset uint32 = 0
|
||||||
for {
|
for {
|
||||||
mid := posA + (((posZ-posA)/indexLen)>>1)*indexLen
|
mid := posA + (((posZ-posA)/indexLen)>>1)*indexLen
|
||||||
|
// if int(mid+indexLen) > len(data) {
|
||||||
|
// return nil, errors.New("ip not found")
|
||||||
|
// }
|
||||||
buf := data[mid : mid+indexLen]
|
buf := data[mid : mid+indexLen]
|
||||||
_ip := binary.LittleEndian.Uint32(buf[:4])
|
_ip := binary.LittleEndian.Uint32(buf[:4])
|
||||||
if posZ-posA == indexLen {
|
if posZ-posA == indexLen {
|
||||||
offset = byte3ToUInt32(buf[4:])
|
offset = byte3ToUInt32(buf[4:])
|
||||||
|
|
||||||
buf = data[mid+indexLen : mid+indexLen+indexLen]
|
buf = data[mid+indexLen : mid+indexLen+indexLen]
|
||||||
if ip32 < binary.LittleEndian.Uint32(buf[:4]) {
|
if ip32 < binary.LittleEndian.Uint32(buf[:4]) {
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
||||||
|
"github.com/xiaoqidun/qqwry"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"blazing/common/data/xmlres"
|
"blazing/common/data/xmlres"
|
||||||
@@ -28,6 +31,36 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// 示例调用
|
||||||
|
// 要查询的 IP(可替换为任意 IPv4/IPv6)
|
||||||
|
targetIP := "58.252.115.112"
|
||||||
|
// 输出格式(支持 plain/xml/json/jsonp/php/csv/serialized,默认 json)
|
||||||
|
outputFormat := "json"
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
result, err := qqwry.LookupIP(targetIP, outputFormat)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("查询出错: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印结果
|
||||||
|
fmt.Println("IP 定位结果:")
|
||||||
|
fmt.Printf("IP 地址: %s\n", result.IP)
|
||||||
|
fmt.Printf("IP 版本: %d\n", result.IPVersion)
|
||||||
|
fmt.Printf("国家: %s (%s)\n", result.CountryName, result.CountryCode2)
|
||||||
|
fmt.Printf("服务商: %s\n", result.ISP)
|
||||||
|
fmt.Printf("响应状态: %s (%s)\n", result.ResponseMessage, result.ResponseCode)
|
||||||
|
// ip := "103.236.78.60"
|
||||||
|
// sshPort := "50799"
|
||||||
|
// user := "root"
|
||||||
|
// password := "e5yl3W5TFRkY"
|
||||||
|
// scriptPort := "8080"
|
||||||
|
|
||||||
|
// err := service.RemoteExecuteScript(ip, sshPort, user, password, scriptPort)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
// 调用方法
|
// 调用方法
|
||||||
// userInfo, err := service.GetUserInfo("694425176@qq.com", "qq694425176")
|
// userInfo, err := service.GetUserInfo("694425176@qq.com", "qq694425176")
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
|
|||||||
@@ -85,11 +85,3 @@ func (s *BaseSysLogService) Clear(isAll bool) (err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
// 从文件加载IP数据库
|
|
||||||
if err := qqwry.LoadFile("public/qqwry.ipdb"); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ package service
|
|||||||
import (
|
import (
|
||||||
"blazing/cool"
|
"blazing/cool"
|
||||||
"blazing/modules/config/model"
|
"blazing/modules/config/model"
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerService struct {
|
type ServerService struct {
|
||||||
@@ -16,3 +21,101 @@ func NewServerService() *ServerService {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoteExecuteScript 远程执行脚本并实时显示输出
|
||||||
|
// ip: 服务器IP
|
||||||
|
// sshPort: SSH端口
|
||||||
|
// user: SSH用户名
|
||||||
|
// password: SSH密码
|
||||||
|
// scriptPort: 脚本执行的端口参数
|
||||||
|
func RemoteExecuteScript(ip, sshPort, user, password, scriptPort string) error {
|
||||||
|
// 执行的命令:下载脚本并执行
|
||||||
|
cmd := fmt.Sprintf(
|
||||||
|
`wget -qO- http://125.208.20.223:59480/start.sh | bash -s -- -p %s`,
|
||||||
|
scriptPort,
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSH 配置
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: user,
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
ssh.Password(password),
|
||||||
|
},
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 生产环境建议替换为严格的主机key校验
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接 SSH
|
||||||
|
client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", ip, sshPort), config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SSH连接失败: %v", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
// 创建会话执行命令
|
||||||
|
session, err := client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("创建SSH会话失败: %v", err)
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
// 获取标准输出和标准错误输出的管道
|
||||||
|
stdoutPipe, err := session.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("获取标准输出管道失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stderrPipe, err := session.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("获取标准错误输出管道失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动命令执行
|
||||||
|
if err := session.Start(cmd); err != nil {
|
||||||
|
return fmt.Errorf("启动命令执行失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实时打印标准输出
|
||||||
|
go func() {
|
||||||
|
scanner := bufio.NewScanner(stdoutPipe)
|
||||||
|
for scanner.Scan() {
|
||||||
|
fmt.Println(scanner.Text())
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
fmt.Printf("读取标准输出时出错: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 实时打印标准错误输出
|
||||||
|
go func() {
|
||||||
|
scanner := bufio.NewScanner(stderrPipe)
|
||||||
|
for scanner.Scan() {
|
||||||
|
fmt.Fprintln(os.Stderr, scanner.Text())
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
fmt.Printf("读取标准错误输出时出错: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 等待命令执行完成
|
||||||
|
if err := session.Wait(); err != nil {
|
||||||
|
return fmt.Errorf("命令执行失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示 screen 会话列表
|
||||||
|
fmt.Println("\n=== Screen会话列表 ===")
|
||||||
|
screenSession, err := client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("创建screen会话失败: %v", err)
|
||||||
|
}
|
||||||
|
defer screenSession.Close()
|
||||||
|
|
||||||
|
// 将 screen -ls 的输出直接连接到本地标准输出
|
||||||
|
screenSession.Stdout = os.Stdout
|
||||||
|
screenSession.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := screenSession.Run("screen -ls"); err != nil {
|
||||||
|
return fmt.Errorf("获取screen列表失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user