个人响应式网站设计,做销售找客户渠道,国内做五金加工的订单网站,佛山网站建设公司分享微信营销的五个技巧本文将分享如何借助当下流行的 AI 工具,一步步完成一个开源项目的开发。 
写在前面 
在写代码时#xff0c;总是会遇到一些有趣的机缘巧合。前几天#xff0c;我在翻看自己之前的开源项目时#xff0c;又看到了 DDNS 相关的讨论。虽然在 2021 年我写过两篇相对详细的教程总是会遇到一些有趣的机缘巧合。前几天我在翻看自己之前的开源项目时又看到了 DDNS 相关的讨论。虽然在 2021 年我写过两篇相对详细的教程《使用 Nginx 提供 DDNS 服务前篇》和《使用 Nginx 提供 DDNS 服务中篇》但总觉得还可以做得更好。 
这几天在上海出差上课本地的网络和算力资源都比较有限。正好借这个机会快速开发一个轻量的小工具顺便也回应下之前有朋友问我的问题在 AI 时代开发一个简单应用的成本到底有多低 
去年五月份我写过一篇文章《AI 加持的代码编写实战快速实现 Nginx 配置格式化工具》当时使用的是 ChatGPT这篇文章中我们来使用代码能力更强的 Anthropic Claude Sonnet 来完成类似的事情。在这篇文章中我会尽可能使用对“非程序员”友好的方法尽量避免使用复杂的 IDE。 
项目已经在 Github 开源 soulteary/ip-helper有需要可以自取如果觉得有帮助的话别忘了“一键三连”支持一下。 
这个开源小工具的交互设计借鉴了 CIP.CC 的 IP 查询工具。 我一直觉得 CIP.CC 是个非常实用的工具。简洁明了能快速提供有价值的参考信息。它整合了三个不同的 IP 数据源。实在要说缺点的话如果能够公开数据库的来源和版本就更棒了。不过在当前国内数据库和数据源要么收费要么需要申请的环境下这类网站可能终将成为互联网的一段历史。 
本文之所以能够成文感谢好朋友高老师IPIP提供基础数据支持这个项目在战胜了各种侵权事件之后IPIP 的数据目前应该是毫无疑问的第一梯队了恭喜 
另外遗憾的是目前该网站的“纯 IP 信息查询”以及“使用 Telnet、FTP 等方式查询”功能已经无法使用。 
所以在这个项目中我会根据自己的理解来实现并补充这些功能。 
好了让我们从前端到后端来折腾出来这个小工具。 
第一步使用多模态模型创建基础 UI 界面 
2024 年底各大模型都在推出“多模态”能力让 AI 不仅能读懂文字还能理解图片、音视频。让我们一步步用这些能力来搭建一个实用的工具界面吧。 
从界面设计开始 
我们可以先让模型帮助我们生成一个简洁的 UI 模块设计图设计一个网页工具左右分栏布局右侧是查询界面。 然后把已有的界面截图Sketch 画一个或使用你想借鉴的产品界面丢给模型提出一个典型的模糊产品需求用 HTML 和 CSS 实现一个类似的精致界面。 接下来我们在新的对话中继续完善布局细节“使用 CSS 和 HTML 创建一个左右分栏布局左侧固定 30%包含 Logo 图片。” 好了界面的设计和代码就都有了接下来我们需要一个吸引眼球的 Logo。 
主视觉  Logo 设计 
这个环节我选择用 Midjourney 来设计“来一只动感的大熊猫”。关于提示词你可以自由发挥创造更酷的版本。如果感兴趣可以参考我在 2023 年 4 月写的文章《八十行代码实现开源的 Midjourney、Stable Diffusion “咒语”作图工具》 图片优化 
生成的图片往往需要进一步调整。你可以用图片编辑软件调整内容、尺寸和格式 如果你是 macOS 用户只想调整图片尺寸用命令行会更快这里我们把宽度设为 380 像素 
sips -Z 380 /Users/soulteary/Downloads/panda.png --out small-panda.png   
/Users/soulteary/Downloads/panda.png/Users/soulteary/Lab/github/ip-helper/small-panda.pngFavicon 制作 
别忘了网站还需要一个 Favicon收藏夹图标。 
我们可以让 AI 基于已有 Logo 设计一个像素化版本“参考图片设计一个简单的马赛克版本的 LOGO”。 完成这些设计后我们就可以把 AI 生成的代码保存下来准备进行下一步的整合处理了。 
组装 AI 生成的界面素材 
组合好的代码素材得到的界面类似下面这张图。 对于AI 生成的界面素材我们该如何组装成一个完整的应用界面呢方法其实很简单。当你有了多个独立的界面组件后可以通过以下方式将它们整合起来 
最简单的方式是创建一个新的AI对话并提供明确的整合需求比如“将查询工具组件集成到左右布局面板的右侧区域。 如果你具备前端开发经验更推荐手动组合这些代码。这样不仅能优化性能还能构建出更合理的代码结构为后续功能扩展打好基础。 
我们得到了界面后接下来就可以来实现基础的后端服务啦。 
第二步完成服务端设计 
后端服务的核心任务是获取和解析用户的 IP 信息并将结果呈现给用户。 
按照经典的模块化思路我们可以把功能划分为以下几个部分Web 界面渲染模块、IP 信息解析模块、IP 信息 API 接口模块以及在原始工具基础上新增的多协议支持包括 Telnet、FTP 等。 
搭建基础服务框架 
接下来我们继续让 AI 助手帮我们生成代码使用 Gin 实现一个简单的服务解析命令行参数和环境变量中的端口和域名信息、以及用户口令。 很快基础框架代码就准备就绪了。这段代码为我们提供了一个运行在 8080 端口的服务器支持通过命令行参数或环境变量来配置服务端口和域名同时具备基于 TOKEN 的用户认证功能。 
完成和模版的交互 
我们先把前面的前端代码保存到项目的 public/index.template.html 文件中同时将 Logo 等静态资源文件也放入 public 目录下。同时根据需要优化程序代码让用户认证和代码交互体验更加自然顺畅。 
另外我们可以搭配使用我在今年年初写的文章《完善 Golang Gin 框架的静态中间件Gin-Static》中介绍的中间件 soulteary/gin-static。这样不仅能让程序支持单文件发布还能提升整体性能。如果你想深入了解相关原理可以参考《深入浅出 Golang 资源嵌入方案前篇》以及查看 Go-Embed 标签下的系列文章。 
package mainimport (embedflagfmtiolognet/httposgithub.com/gin-gonic/ginstatic github.com/soulteary/gin-static
)type Config struct {Domain stringPort   stringToken  string
}// 解析配置参数
func parseConfig() *Config {config : Config{}// 解析命令行参数flag.StringVar(config.Port, port, , 服务器端口)flag.StringVar(config.Domain, domain, , 服务器域名)flag.StringVar(config.Token, token, , API 访问令牌)flag.Parse()// 尝试从环境变量中获取未设置的内容if config.Port   {config.Port  os.Getenv(SERVER_PORT)}if config.Domain   {config.Domain  os.Getenv(SERVER_DOMAIN)}if config.Token   {config.Token  os.Getenv(TOKEN)}// 使用默认值if config.Port   {config.Port  8080}if config.Domain   {config.Domain  localhost}if config.Token   {config.Token  log.Println(提醒为了提高安全性可以设置 TOKEN 环境变量或 token 命令行参数)}return config
}// 验证请求中的令牌
func authMiddleware(config *Config) gin.HandlerFunc {return func(c *gin.Context) {if config.Token !  {token : c.Query(token)if token   {token  c.GetHeader(X-Token)}if token ! config.Token {c.JSON(401, gin.H{error: 无效的认证令牌})c.Abort()return}}c.Next()}
}func Get(link string) ([]byte, error) {resp, err : http.Get(link)if err ! nil {return nil, err}defer resp.Body.Close()if resp.StatusCode ! http.StatusOK {return nil, fmt.Errorf(服务器返回非200状态码: %d, resp.StatusCode)}body, err : io.ReadAll(resp.Body)if err ! nil {return nil, fmt.Errorf(读取响应内容失败: %v, err)}return body, nil
}//go:embed public
var EmbedFS embed.FSfunc main() {config : parseConfig()r : gin.Default()r.GET(/health, func(c *gin.Context) {c.JSON(200, gin.H{status: ok,domain: config.Domain,})})r.Use(static.Serve(/, static.LocalFile(./public, false)))r.Use(authMiddleware(config))r.GET(/, func(c *gin.Context) {buf, err : Get(fmt.Sprintf(http://localhost:%s/index.template.html, config.Port))if err ! nil {c.String(500, 读取模板文件失败: %v, err)return}c.Data(200, text/html; charsetutf-8, buf)})serverAddr : fmt.Sprintf(:%s, config.Port)log.Printf(启动服务器于 %s:%s\n, config.Domain, config.Port)if err : r.Run(serverAddr); err ! nil {log.Fatalf(启动服务器失败: %v, err)}
}IP 获取和基础分析功能的实现 
在与模型的进一步对话中我们实现更核心的功能使用 Golang Gin 框架来获取用户访问时的 IP 信息并判断请求是否经过了代理服务器。 获得这段代码后我们将它与之前的功能进行整合新增代码有注释。现在我们的服务不仅可以获取用户的 IP 信息还能够基础地判断请求是否通过代理服务器转发。 
除了网页展示外我们还新增了一个 /ip 接口让用户可以直接通过程序获取纯 IP 信息提供了更灵活的使用方式。 
// ...// IPInfo 存储 IP 相关信息
type IPInfo struct {ClientIP     string json:client_ipProxyIP      string json:proxy_ip,omitemptyIsProxy      bool   json:is_proxyForwardedFor string json:forwarded_for,omitemptyRealIP       string json:real_ip
}// 获取并分析 IP 信息的中间件
func IPAnalyzer() gin.HandlerFunc {return func(c *gin.Context) {ipInfo : analyzeIP(c)// 将 IP 信息存储到上下文中c.Set(ip_info, ipInfo)c.Next()}
}// 分析 IP 信息
func analyzeIP(c *gin.Context) IPInfo {var ipInfo IPInfo// 获取客户端 IPipInfo.ClientIP  c.ClientIP()// 获取 X-Forwarded-For 头信息forwardedFor : c.GetHeader(X-Forwarded-For)if forwardedFor !  {ipInfo.ForwardedFor  forwardedFor// X-Forwarded-For 可能包含多个 IP第一个是原始客户端 IPips : strings.Split(forwardedFor, ,)if len(ips)  0 {ipInfo.RealIP  strings.TrimSpace(ips[0])if len(ips)  1 {ipInfo.IsProxy  trueipInfo.ProxyIP  strings.TrimSpace(ips[len(ips)-1])}}} else {ipInfo.RealIP  ipInfo.ClientIP}// 获取 X-Real-IP 头信息xRealIP : c.GetHeader(X-Real-IP)if xRealIP !   xRealIP ! ipInfo.RealIP {ipInfo.IsProxy  trueipInfo.ProxyIP  ipInfo.ClientIPipInfo.RealIP  xRealIP}// 检查是否为私有 IPif isPrivateIP(ipInfo.ClientIP) {ipInfo.IsProxy  true}return ipInfo
}// 检查是否为私有 IP 地址
func isPrivateIP(ipStr string) bool {ip : net.ParseIP(ipStr)if ip  nil {return false}// 检查是否为私有 IP 范围privateIPRanges : []struct {start net.IPend   net.IP}{{net.ParseIP(10.0.0.0), net.ParseIP(10.255.255.255)},{net.ParseIP(172.16.0.0), net.ParseIP(172.31.255.255)},{net.ParseIP(192.168.0.0), net.ParseIP(192.168.255.255)},}for _, r : range privateIPRanges {if bytes.Compare(ip, r.start)  0  bytes.Compare(ip, r.end)  0 {return true}}return false
}//go:embed public
var EmbedFS embed.FSfunc main() {// ...r.Use(static.Serve(/, static.LocalFile(./public, false)))r.Use(authMiddleware(config))// 使用IP分析中间件r.Use(IPAnalyzer())r.GET(/, func(c *gin.Context) {// 先获取 IP 信息ipInfo, exists : c.Get(ip_info)if !exists {c.JSON(500, gin.H{error: IP info not found})return}buf, err : Get(fmt.Sprintf(http://localhost:%s/index.template.html, config.Port))if err ! nil {c.String(500, 读取模板文件失败: %v, err)return}// TODO 将 IP 信息传递给模板fmt.Println(ipInfo)c.Data(200, text/html; charsetutf-8, buf)})// 单独提供一个接口来获取 IP 信息r.GET(/ip, func(c *gin.Context) {ipInfo, exists : c.Get(ip_info)if !exists {c.JSON(500, gin.H{error: IP info not found})return}c.JSON(200, ipInfo)})serverAddr : fmt.Sprintf(:%s, config.Port)log.Printf(启动服务器于 %s:%s\n, config.Domain, config.Port)if err : r.Run(serverAddr); err ! nil {log.Fatalf(启动服务器失败: %v, err)}r.Run(:8080)
}启动程序后我们可以通过命令行或者直接在浏览器中访问 http://localhost:8080/ip 来测试功能。比如使用 curl 命令 
# curl 127.0.0.1:8080/ip{client_ip:127.0.0.1,is_proxy:false,real_ip:127.0.0.1}看到这个返回结果说明我们的基础功能已经正常运行了。 
接下来我们先不着急处理模板渲染的部分而是把注意力放在 IP 信息和数据库对接这个核心模块上。 
完成 IP 数据库查询功能 
在2020年时因业务需求我曾使用过高老师的 IP 库通过阿里云购买并写过两篇关于如何处理本地数据的高性能方案文章《阿里云 IP 地理位置库淘宝IP库实践前篇》和《阿里云 IP 地理位置库淘宝IP库实践后篇》。 
这次为了开发这个小工具我向高老师获取了精简版数据和解析文档。由于我只需要像文章开头提到的那样解析基础地理信息所以我选择 fork 了一个 Go SDK 并进行了简化处理。 
这次为了完成这个小工具和高老师要来了精简版的数据以及解析文档因为我只想和文章开头一样解析出基础的地理信息所以我 fork 了一个 Go SDK 版本并做了 “青春版化” 处理。 
首先在项目目录中执行以下命令来下载简化版 SDK 
go get github.com/soulteary/ipdb-go接下来我们将在之前的代码基础上添加查询功能并新增一个 /ip/:ip 路由让用户可以查询指定 IP 的数据。 
// ...// 帮助我们对数据库中的内容进行去重
// eg: [CLOUDFLARE.COM,CLOUDFLARE.COM,]  [CLOUDFLARE.COM,]func removeDuplicates(strSlice []string) []string {// 创建一个 map 用于存储唯一的字符串encountered : make(map[string]bool)result : []string{}// 遍历切片将未出现过的字符串添加到结果中for _, str : range strSlice {if !encountered[str] {encountered[str]  trueresult  append(result, str)}}return result
}//go:embed public
var EmbedFS embed.FSfunc main() {config : parseConfig()// 初始化 IP 数据库db, err : ipdb.NewCity(./data/ipipfree.ipdb)if err ! nil {log.Fatal(err)}// 更新 ipdb 文件后可调用 Reload 方法重新加载内容// db.Reload(./data/ipipfree.ipdb)r : gin.Default()r.GET(/health, func(c *gin.Context) {c.JSON(200, gin.H{status: ok,domain: config.Domain,})})r.Use(static.Serve(/, static.LocalFile(./public, false)))r.Use(authMiddleware(config))r.Use(IPAnalyzer())r.GET(/, func(c *gin.Context) {ipInfo, exists : c.Get(ip_info)if !exists {c.JSON(500, gin.H{error: IP info not found})return}buf, err : Get(fmt.Sprintf(http://localhost:%s/index.template.html, config.Port))if err ! nil {c.String(500, 读取模板文件失败: %v, err)return}// TODO 将 IP 信息传递给模板fmt.Println(ipInfo)c.Data(200, text/html; charsetutf-8, buf)})// 获取当前请求方的 IP 地址信息r.GET(/ip, func(c *gin.Context) {ipInfo, exists : c.Get(ip_info)if !exists {c.JSON(500, gin.H{error: IP info not found})return}c.JSON(200, ipInfo)})// 获取指定 IP 地址信息r.GET(/ip/:ip, func(c *gin.Context) {// 获取 URL 中的 IP 地址ipaddr : c.Param(ip)fmt.Println(ip, ipaddr)if ipaddr   {ipInfo, exists : c.Get(ip_info)if !exists {c.JSON(500, gin.H{error: IP info not found})return}ipaddr  ipInfo.(IPInfo).RealIP}dbInfo, err : db.Find(ipaddr, CN)if err ! nil {dbInfo  []string{未找到 IP 地址信息}}dbInfo  removeDuplicates(dbInfo)c.JSON(200, map[string]any{ip: ipaddr, info: dbInfo})})serverAddr : fmt.Sprintf(:%s, config.Port)log.Printf(启动服务器于 %s:%s\n, config.Domain, config.Port)if err : r.Run(serverAddr); err ! nil {log.Fatalf(启动服务器失败: %v, err)}r.Run(:8080)
}让我们通过命令行或浏览器来验证服务是否正常运行。我们可以测试几个不同的 IP 地址: 
首先测试获取当前请求来源的 IP 信息。 
# curl 127.0.0.1:8080/ip{client_ip:127.0.0.1,is_proxy:false,real_ip:127.0.0.1}然后测试查询特定IP地址。 
# curl 127.0.0.1:8080/ip/123.123.123.123{info:[中国,北京],ip:123.123.123.123}最后测试一个 CloudFlare 的 IP: 
# curl 127.0.0.1:8080/ip/1.1.1.1{info:[CLOUDFLARE.COM,],ip:1.1.1.1}第三步从静态页面到动态网站数据与界面的整合 
我们已经完成了基础架构的搭建工作现在要进入最后也是最关键的阶段将数据层和展示层打通让整个系统真正运转起来。让我们一步步来实现这个目标。 
模版和服务数据联动 
第一步我们需要改造之前的静态模板。我们要把原本写死的数据替换成程序可以动态填充的占位符 接下来我们先实现一个基础版本的IP信息查询功能当用户访问网站首页时系统会自动获取访问者的IP地址并展示相关的IP信息。 
// ...
func main() {// ...r.GET(/, func(c *gin.Context) {ipInfo, exists : c.Get(ip_info)if !exists {c.JSON(500, gin.H{error: IP info not found})return}// 查询 IP 地址具体信息dbInfo, err : db.Find(ipInfo.(IPInfo).RealIP, CN)if err ! nil {dbInfo  []string{未找到 IP 地址信息}}// 读取默认模版template, err : Get(fmt.Sprintf(http://localhost:%s/index.template.html, config.Port))if err ! nil {c.String(500, 读取模板文件失败: %v, err)return}// 更新模版中的 IP 地址template  bytes.ReplaceAll(template, []byte(%IP_ADDR%), []byte(ipInfo.(IPInfo).ClientIP))// 更新模版中的域名template  bytes.ReplaceAll(template, []byte(%DOMAIN%), []byte(config.Domain))// 更新模版中的 IP 地址信息template  bytes.ReplaceAll(template, []byte(%DATA_1_INFO%), []byte(strings.Join(removeDuplicates(dbInfo),  )))c.Data(200, text/html; charsetutf-8, template)})
// ...
}完成模板更新后我们需要启动服务来验证功能。使用以下命令启动 
SERVER_DOMAINlocalhost:8080 go run main.go启动服务后打开浏览器访问 localhost:8080我们就可以看到如下界面 从界面可以看到页面的数据联动功能已经正常工作。不过目前使用的数据库还不支持 IPv6 地址的查询需要使用商业版本或增加其他数据库导致部分信息展示不符合预期。没关系接下来我们就来实现按指定 IP 查询的功能。 
后端处理前端用户输入 
为了让用户能够与我们的应用进行交互现在让我们对之前的静态 HTML 模板做一些优化。我们将添加一个表单来处理用户输入的 IP 地址。 
首先在 HTML 模版中添加数据表单 
div classsearch-containerform action/ methodpostinput typetext nameip classsearch-input placeholder请输入要查询的 IP 地址 value%IP_ADDR% /button classsearch-button typesubmit查询/button  /form
/div在这段代码中 
使用 form 标签创建表单设置 action/ 将数据提交到根路径methodpost 指定使用 POST 方法提交数据输入框中的 value%IP_ADDR% 用于回显用户之前输入的 IP 地址 
接下来我们需要在后端添加相应的处理逻辑 
// ...
// 使用 net 包验证 IP 地址
func isValidIPAddress(ip string) bool {if parsedIP : net.ParseIP(ip); parsedIP ! nil {return true}return false
}// IPForm 定义表单结构
type IPForm struct {IP string form:ip binding:required
}func main() {// ...// 处理 POST 请求解析表单数据r.POST(/, func(c *gin.Context) {// 获取请求中的 IP 地址信息ipInfo, exists : c.Get(ip_info)if !exists {c.JSON(500, gin.H{error: IP info not found})return}// 默认 IP 地址为空ip : var form IPForm// 使用 ShouldBind 绑定表单数据if err : c.ShouldBind(form); err ! nil {// 如果绑定失败使用请求中的 IP 地址ip  ipInfo.(IPInfo).RealIP} else {// 获取到 IP 地址后的处理逻辑ip  form.IP// 如果 IP 地址不合法使用请求中的 IP 地址if !isValidIPAddress(ip) {ip  ipInfo.(IPInfo).RealIP}}c.Redirect(302, fmt.Sprintf(/ip/%s, ip))})
// ...
}程序首先会记录发起请求的客户端 IP。然后检查用户通过表单提交的 IP 地址是否正确。如果 IP 地址正确会自动跳转到类似 /ip/123.123.123.123 这样的地址来展示 IP 详细信息。如果提交的 IP 地址无效则会使用客户端的实际 IP 地址进行跳转。 
打造统一的接口适配多种场景 
细心的朋友可能注意到了前面提到的 /ip/:ip 接口原本是为命令行工具设计的默认返回 JSON 格式数据而不是网页界面。在 CIP 网站的设计中浏览器访问和命令行调用使用了不同的接口地址。不过通过一些技巧我们完全可以让同一个接口同时支持这两种使用场景。 
先来将 IP 获取和信息查询以及渲染部分分别抽象为独立的模块 
// ...
func main() {//...// 获取客户端 IP 信息getClientIPInfo : func(c *gin.Context, ipaddr string) (resultIP string, resultDBInfo []string, err error) {// 判断是否有传入 IP 地址if ipaddr   {// 如果没有有效 IP默认使用发起请求的客户端 IP 信息ipInfo, exists : c.Get(ip_info)if !exists {return resultIP, resultDBInfo, fmt.Errorf(IP info not found)}ipaddr  ipInfo.(IPInfo).RealIP}dbInfo, err : db.Find(ipaddr, CN)if err ! nil {dbInfo  []string{未找到 IP 地址信息}}dbInfo  removeDuplicates(dbInfo)return ipaddr, dbInfo, nil}// 渲染模板renderTemplate : func(globalTemplate []byte, ipaddr string, dbInfo []string) []byte {template : bytes.ReplaceAll(globalTemplate, []byte(%IP_ADDR%), []byte(ipaddr))template  bytes.ReplaceAll(template, []byte(%DOMAIN%), []byte(config.Domain))template  bytes.ReplaceAll(template, []byte(%DATA_1_INFO%), []byte(strings.Join(removeDuplicates(dbInfo),  )))return template}// 渲染 JSONrenderJSON : func(ipaddr string, dbInfo []string) map[string]any {return map[string]any{ip: ipaddr, info: dbInfo}}globalTemplate : []byte{}r.GET(/, func(c *gin.Context) {// 预缓存模板文件if len(globalTemplate)  0 {globalTemplate, err  Get(fmt.Sprintf(http://localhost:%s/index.template.html, config.Port))if err ! nil {log.Fatalf(读取模板文件失败: %v\n, err)return}}// 获取客户端 IP 信息首页不需要传入 IP 地址ipAddr, dbInfo, err : getClientIPInfo(c, )if err ! nil {c.JSON(500, gin.H{error: err.Error()})return}// 返回渲染后的 HTML 内容c.Data(200, text/html; charsetutf-8, renderTemplate(globalTemplate, ipAddr, dbInfo))})r.GET(/ip/:ip, func(c *gin.Context) {ip : c.Param(ip)// 获取指定 IP 地址的信息ipAddr, dbInfo, err : getClientIPInfo(c, ip)if err ! nil {c.JSON(500, gin.H{error: err.Error()})return}c.JSON(200, renderJSON(ipAddr, dbInfo))})// ...
}接下来我们要实现一个功能自动识别访问请求是来自类似 curl 的命令行工具还是来自浏览器。 
// ...
// 判断请求发起方是否为“下载工具”
func IsDownloadTool(userAgent string) bool {// 转换为小写以便不区分大小写比较ua : strings.ToLower(userAgent)// 常见下载工具的特征字符串downloadTools : []string{curl,wget,aria2,python-requests,axios,got,postman,}for _, tool : range downloadTools {if strings.Contains(ua, tool) {return true}}return false
}func main() {// ...r.GET(/, func(c *gin.Context) {if len(globalTemplate)  0 {globalTemplate, err  Get(fmt.Sprintf(http://localhost:%s/index.template.html, config.Port))if err ! nil {log.Fatalf(读取模板文件失败: %v\n, err)return}}ipAddr, dbInfo, err : getClientIPInfo(c, )if err ! nil {c.JSON(500, gin.H{error: err.Error()})return}// 获取请求头中的 User-Agent 信息userAgent : c.GetHeader(User-Agent)// 使用下载工具访问时返回 JSON 格式if IsDownloadTool(userAgent) {c.JSON(200, renderJSON(ipAddr, dbInfo))} else {c.Data(200, text/html; charsetutf-8, renderTemplate(globalTemplate, ipAddr, dbInfo))}})r.GET(/ip/:ip, func(c *gin.Context) {ip : c.Param(ip)ipAddr, dbInfo, err : getClientIPInfo(c, ip)if err ! nil {c.JSON(500, gin.H{error: err.Error()})return}// 获取请求头中的 User-Agent 信息userAgent : c.GetHeader(User-Agent)// 使用下载工具访问时返回 JSON 格式if IsDownloadTool(userAgent) {c.JSON(200, renderJSON(ipAddr, dbInfo))} else {c.Data(200, text/html; charsetutf-8, renderTemplate(globalTemplate, ipAddr, dbInfo))}})// ...
}经过上面的改进不管是访问根路径 / 还是 /ip/:ip 接口程序都能根据访问方式自动返回合适的格式。浏览器访问会看到格式化的页面命令行工具访问则获得纯文本结果。这样一来我们其实可以考虑是否要保留之前专门为命令行工具设计的 /ip 接口因为现在 / 已经能够处理这两种场景了。当然如果特别在意性能保留专门的接口也是一种选择。 和之前一样重启程序后我们可以打开浏览器做个简单测试。随便输入一个 IP 地址进行查询你会发现一切都在按照预期正常运行。 
最后 
到这里我们已经实现了这个应用的核心功能。在下一篇文章中我们将继续探讨本文中的一些遗留问题看看如何借助 AI 的力量来帮助我们更快地完成应用开发。 
–EOF 我们有一个小小的折腾群里面聚集了一些喜欢折腾、彼此坦诚相待的小伙伴。 
我们在里面会一起聊聊软硬件、HomeLab、编程上、生活里以及职场中的一些问题偶尔也在群里不定期的分享一些技术资料。 
关于交友的标准请参考下面的文章 
致新朋友为生活投票不断寻找更好的朋友 
当然通过下面这篇文章添加好友时请备注实名和公司或学校、注明来源和目的珍惜彼此的时间  
关于折腾群入群的那些事 如果你觉得内容还算实用欢迎点赞分享给你的朋友在此谢过。 
如果你想更快的看到后续内容的更新请戳 “点赞”、“分享”、“在看” 这些免费的鼓励将会影响后续有关内容的更新速度。 本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议欢迎转载、或重新修改使用但需要注明来源。 署名 4.0 国际 (CC BY 4.0) 
本文作者: 苏洋 
创建时间: 2024年12月21日 统计字数: 17445字 阅读时间: 35分钟阅读 本文链接: https://soulteary.com/2024/12/21/use-ai-to-assist-in-developing-an-open-source-ip-information-tool-part-1.html 文章转载自: http://www.morning.bysey.com.gov.cn.bysey.com http://www.morning.sbdqy.cn.gov.cn.sbdqy.cn http://www.morning.ztrht.cn.gov.cn.ztrht.cn http://www.morning.qfplp.cn.gov.cn.qfplp.cn http://www.morning.rhmpk.cn.gov.cn.rhmpk.cn http://www.morning.bhqlj.cn.gov.cn.bhqlj.cn http://www.morning.nqmdc.cn.gov.cn.nqmdc.cn http://www.morning.njftk.cn.gov.cn.njftk.cn http://www.morning.jbpodhb.cn.gov.cn.jbpodhb.cn http://www.morning.mnjyf.cn.gov.cn.mnjyf.cn http://www.morning.wqkfm.cn.gov.cn.wqkfm.cn http://www.morning.newfeiya.com.cn.gov.cn.newfeiya.com.cn http://www.morning.htmhl.cn.gov.cn.htmhl.cn http://www.morning.pwggd.cn.gov.cn.pwggd.cn http://www.morning.enjoinfo.cn.gov.cn.enjoinfo.cn http://www.morning.ghyfm.cn.gov.cn.ghyfm.cn http://www.morning.lcxzg.cn.gov.cn.lcxzg.cn http://www.morning.gycyt.cn.gov.cn.gycyt.cn http://www.morning.mxcgf.cn.gov.cn.mxcgf.cn http://www.morning.qphcq.cn.gov.cn.qphcq.cn http://www.morning.sqfnx.cn.gov.cn.sqfnx.cn http://www.morning.sbkb.cn.gov.cn.sbkb.cn http://www.morning.rhmpk.cn.gov.cn.rhmpk.cn http://www.morning.smspc.cn.gov.cn.smspc.cn http://www.morning.sxfnf.cn.gov.cn.sxfnf.cn http://www.morning.yqlrq.cn.gov.cn.yqlrq.cn http://www.morning.rxfbf.cn.gov.cn.rxfbf.cn http://www.morning.xgchm.cn.gov.cn.xgchm.cn http://www.morning.lqytk.cn.gov.cn.lqytk.cn http://www.morning.pxrfm.cn.gov.cn.pxrfm.cn http://www.morning.qzmnr.cn.gov.cn.qzmnr.cn http://www.morning.npkrm.cn.gov.cn.npkrm.cn http://www.morning.wrkcw.cn.gov.cn.wrkcw.cn http://www.morning.jcwt.cn.gov.cn.jcwt.cn http://www.morning.nrchx.cn.gov.cn.nrchx.cn http://www.morning.llcsd.cn.gov.cn.llcsd.cn http://www.morning.hbhnh.cn.gov.cn.hbhnh.cn http://www.morning.wjplm.cn.gov.cn.wjplm.cn http://www.morning.ltqzq.cn.gov.cn.ltqzq.cn http://www.morning.fqyqm.cn.gov.cn.fqyqm.cn http://www.morning.bsrcr.cn.gov.cn.bsrcr.cn http://www.morning.jncxr.cn.gov.cn.jncxr.cn http://www.morning.kchwr.cn.gov.cn.kchwr.cn http://www.morning.mrccd.cn.gov.cn.mrccd.cn http://www.morning.xrhst.cn.gov.cn.xrhst.cn http://www.morning.wlddq.cn.gov.cn.wlddq.cn http://www.morning.jhrlk.cn.gov.cn.jhrlk.cn http://www.morning.rdnpg.cn.gov.cn.rdnpg.cn http://www.morning.tndxg.cn.gov.cn.tndxg.cn http://www.morning.ntwxt.cn.gov.cn.ntwxt.cn http://www.morning.gnhsg.cn.gov.cn.gnhsg.cn http://www.morning.glpxx.cn.gov.cn.glpxx.cn http://www.morning.qfnrx.cn.gov.cn.qfnrx.cn http://www.morning.cjmmn.cn.gov.cn.cjmmn.cn http://www.morning.rdxp.cn.gov.cn.rdxp.cn http://www.morning.klwxh.cn.gov.cn.klwxh.cn http://www.morning.dgckn.cn.gov.cn.dgckn.cn http://www.morning.nrjr.cn.gov.cn.nrjr.cn http://www.morning.nckzt.cn.gov.cn.nckzt.cn http://www.morning.hhrpy.cn.gov.cn.hhrpy.cn http://www.morning.qjsxf.cn.gov.cn.qjsxf.cn http://www.morning.jftl.cn.gov.cn.jftl.cn http://www.morning.bmpjp.cn.gov.cn.bmpjp.cn http://www.morning.ngznq.cn.gov.cn.ngznq.cn http://www.morning.wnhml.cn.gov.cn.wnhml.cn http://www.morning.fhtmp.cn.gov.cn.fhtmp.cn http://www.morning.ndrzq.cn.gov.cn.ndrzq.cn http://www.morning.ptmsk.cn.gov.cn.ptmsk.cn http://www.morning.qhydkj.com.gov.cn.qhydkj.com http://www.morning.bpp999.com.gov.cn.bpp999.com http://www.morning.ppqzb.cn.gov.cn.ppqzb.cn http://www.morning.gmmyn.cn.gov.cn.gmmyn.cn http://www.morning.wdxr.cn.gov.cn.wdxr.cn http://www.morning.zdhxm.com.gov.cn.zdhxm.com http://www.morning.nrzbq.cn.gov.cn.nrzbq.cn http://www.morning.wxwall.com.gov.cn.wxwall.com http://www.morning.plpqf.cn.gov.cn.plpqf.cn http://www.morning.lkgqb.cn.gov.cn.lkgqb.cn http://www.morning.sjjq.cn.gov.cn.sjjq.cn http://www.morning.nspbj.cn.gov.cn.nspbj.cn