当前位置: 首页 > news >正文

GO学习记录六——集成Swagger接口测试页面

一、集成swagger页面
添加了swagger组件,提供接口测试页面。
只改变了main.go文件。

package mainimport ("context""encoding/json""fmt""log""net/http""regexp""time""github.com/gin-gonic/gin""github.com/jackc/pgx/v5""github.com/jackc/pgx/v5/pgxpool"swaggerFiles "github.com/swaggo/files"ginSwagger "github.com/swaggo/gin-swagger"// 注意:替换为你项目的实际路径// _ "your_project/docs" // docs 包,由 swag 生成// 如果 docs 包在根目录,且 main.go 也在根目录,可以这样导入_ "HTTPServices/docs" // 假设 docs 目录在项目根目录下
)var db *pgxpool.Pool// 启动函数
func main() {// 初始化数据库连接db = InitDB()defer db.Close()// 注册路由RegisterRouter()// 启动 HTTP 服务go func() {StartHTTPServer()}()// 启动 HTTP api测试服务go func() {StartDebugHTTPServer()}()// 阻塞主线程select {}
}// @title           Sample API
// @version         1.0
// @description     API测试页面
// @host      localhost:8080
func StartDebugHTTPServer() {r := gin.Default()// --- 挂载 Swagger UI ---// 访问 http://localhost:8081/swagger/index.html 查看 UIr.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))port := ":8081"LogSuccess("启动 HTTP Swagger测试服务启动,监听端口 %s\n", port)// 启动服务器debugApiError := r.Run(port)if debugApiError != nil {LogError("HTTP api测试服务启动失败:%v", debugApiError)} else {LogSuccess("HTTP api测试服务已启动,监听端口 8081")}
}// 启动 HTTP 服务
func StartHTTPServer() {address := "127.0.0.1:8080" //配置连接ip端口//配置跨域,是影响调试页面不能访问8080相关地址的原因handler := corsMiddleware(http.DefaultServeMux)LogSuccess("启动 HTTP 服务,监听端口 %s\n", address)err := http.ListenAndServe(address, handler)if err != nil {log.Fatalf("服务器启动失败:%v", err)}
}// corsMiddleware 是一个中间件,用于添加 CORS 头
func corsMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {// 设置 CORS 响应头w.Header().Add("Access-Control-Allow-Origin", "http://localhost:8081") // ✅ 修改为你的前端地址w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH")w.Header().Set("Access-Control-Allow-Headers","Origin, Content-Type, Accept, Authorization, X-Requested-With")// 如果需要传递 Cookie 或 Authorization Bearer Tokenw.Header().Set("Access-Control-Allow-Credentials", "true")// 处理预检请求 (OPTIONS)if r.Method == "OPTIONS" {w.WriteHeader(http.StatusOK)return}// 调用下一个处理器 (即注册的路由)next.ServeHTTP(w, r)})
}// 注册路由
func RegisterRouter() {http.HandleFunc("/", helloHandler)    //http://localhost:8080/http.HandleFunc("/time", timeHandler) //http://localhost:8080/time//查询http.HandleFunc("/findTable", findTableNameHandler) //http://localhost:8080/findTable?tableName=name//添加http.HandleFunc("/addTable1", addTable1Handler) //http://localhost:8080/addTable1//删除http.HandleFunc("/deleteTableValue", deleteTableHandler) //http://localhost:8080/deleteTableValue?tableName=table1&fieldName=test1&fieldValue=123test//修改http.HandleFunc("/updateTableValue", updateTableHandler) //http://localhost:8080/updateTableValue?tableName=table1&findFieldName=test1&findFieldValue=hello&setFieldName=test3&setFieldValue=456}// APIResponse 定义了统一的 API 响应格式
type APIResponse struct {Success   bool        `json:"success"`           // 是否成功Status    int         `json:"status"`            // HTTP 状态码Message   string      `json:"message,omitempty"` // 简短消息 报错时的提示信息Data      interface{} `json:"data,omitempty"`    // 主要数据内容Timestamp string      `json:"timestamp"`         // 时间戳 (秒)
}// SendJSONResponse 封装了 JSON 响应的发送逻辑
func SendJSONResponse(w http.ResponseWriter, success bool, status int, message string, data interface{}) {// 设置 Content-Typew.Header().Set("Content-Type", "application/json")// 设置 HTTP 状态码w.WriteHeader(status)// 构造响应体response := APIResponse{Success:   success,Status:    status,Message:   message,Data:      data,Timestamp: time.Now().Format("2006-01-02 15:04:05"), // 当前时间戳格式化}// 编码并发送 JSONif err := json.NewEncoder(w).Encode(response); err != nil {// 如果编码失败,记录错误(但不能再次写入 w,因为 Header 已经发送)http.Error(w, "Internal Server Error", http.StatusInternalServerError)// log.Printf("JSON encode error: %v", err) // 取消注释以记录日志}
}// @Summary      根目录测试连接
// @Description
// @Tags         tags1
// @Accept       json
// @Produce      json
// @Router       / [get]
func helloHandler(w http.ResponseWriter, r *http.Request) {LogInfo("访问路径:%s,来源:%s\n", r.URL.Path, r.RemoteAddr)// 编码 JSON 响应SendJSONResponse(w, true, http.StatusOK, "成功", "Hello, World! 👋")
}// @Summary      查询服务器时间
// @Description
// @Tags         tags1
// @Accept       json
// @Produce      json
// @Router       /time [get]
func timeHandler(w http.ResponseWriter, r *http.Request) {LogInfo("访问路径:%s,来源:%s\n", r.URL.Path, r.RemoteAddr)currentTime := time.Now().Format("2006-01-02 15:04:05")// ✅ 设置响应头SendJSONResponse(w, true, http.StatusOK, "成功", currentTime)
}// @Summary      修改指定表名中,find字段名等于指定值的set字段名的数据
// @Description  根据提供的表名、find字段名、find字段值、set字段名、set字段值,修改数据库中的数据。
// @Tags         tags1
// @Produce      json
// @Param        tableName query string true  "要查询的数据库表名" default(table1)
// @Param        fieldName query string true  "要查询的字段名"
// @Param        fieldValue query string true  "要查询的字段值"
// @Param        setFieldName query string true  "要更新的字段名"
// @Param        setFieldValue query string true  "要更新的字段值"
// @Router       /updateTableValue [get]
func updateTableHandler(w http.ResponseWriter, r *http.Request) {// 解析请求参数tableName := r.URL.Query().Get("tableName")findFieldName := r.URL.Query().Get("findFieldName")findFieldValue := r.URL.Query().Get("findFieldValue")setFieldName := r.URL.Query().Get("setFieldName")setFieldValue := r.URL.Query().Get("setFieldValue")// 完整的参数验证if tableName == "" || findFieldName == "" || setFieldName == "" {http.Error(w, "缺少必要参数", http.StatusBadRequest)return}// 🔐 白名单验证 - 只允许预定义的表和字段allowedTables := map[string]bool{"table1": true, "table2": true}allowedFields := map[string]bool{"test1": true, "test2": true, "test3": true,"test4": true, "test5": true, "test6": true, "test7": true,}if !allowedTables[tableName] {http.Error(w, "不允许的表名", http.StatusBadRequest)return}if !allowedFields[findFieldName] || !allowedFields[setFieldName] {http.Error(w, "不允许的字段名", http.StatusBadRequest)return}// ✅ 使用参数化查询,表名和字段名通过白名单验证后拼接query := fmt.Sprintf("UPDATE %s SET %s = $1 WHERE %s = $2",tableName, setFieldName, findFieldName,)result, err := db.Exec(context.Background(), query, setFieldValue, findFieldValue)if err != nil {http.Error(w, "更新数据失败: "+err.Error(), http.StatusInternalServerError)return}// 检查是否实际更新了数据rowsAffected := result.RowsAffected()if rowsAffected == 0 {http.Error(w, "未找到匹配的数据进行更新", http.StatusNotFound)return}SendJSONResponse(w, true, http.StatusOK, "成功", fmt.Sprintf("%d 行已更新", rowsAffected))}// @Summary      删除指定表名中,指定字段名等于指定值的数据
// @Description  根据提供的表名和字段名和值,删除数据库中的数据。
// @Tags         tags1
// @Produce      json
// @Param        tableName query string true  "要删除的数据库表名"
// @Param        fieldName query string true  "要删除的字段名"
// @Param        fieldValue query string true  "要删除的字段值"
// @Router       /deleteTableValue [get]
func deleteTableHandler(w http.ResponseWriter, r *http.Request) {// 解析请求参数tableName := r.URL.Query().Get("tableName")fieldName := r.URL.Query().Get("fieldName")fieldValue := r.URL.Query().Get("fieldValue")if tableName == "" || fieldName == "" || fieldValue == "" {http.Error(w, "参数错误", http.StatusBadRequest)return}// 执行 SQL 语句,使用参数化查询query := fmt.Sprintf("DELETE FROM %s WHERE %s = $1", tableName, fieldName)_, err := db.Exec(context.Background(), query, fieldValue)if err != nil {http.Error(w, "删除数据失败: "+err.Error(), http.StatusInternalServerError)return}SendJSONResponse(w, true, http.StatusOK, "成功", "数据已删除")
}// @Summary      向table1表中添加数据,字段名=test1,test2,test3,test4,test5,test6,test7
// @Description  根据提供的json数据,向数据库table1中添加数据。
// @Tags         tags1
// @Produce      json
// @Param        data body string true "要插入的数据对象"
// @Router       /addTable1 [post]
func addTable1Handler(w http.ResponseWriter, r *http.Request) {// 定义需要插入的数据结构type requestData struct {Test1 string    `json:"test1"`Test2 time.Time `json:"test2"`Test3 uint32    `json:"test3"`Test4 string    `json:"test4"`Test5 float64   `json:"test5"`Test6 int32     `json:"test6"`Test7 float64   `json:"test7"`}// 解析请求参数var data requestDataerr := json.NewDecoder(r.Body).Decode(&data)if err != nil {http.Error(w, "解析请求参数失败: "+err.Error(), http.StatusBadRequest)return}// 执行 SQL 语句,使用参数化查询query := "INSERT INTO table1 (test1, test2, test3, test4, test5, test6, test7) VALUES ($1, $2, $3, $4, $5, $6, $7)"_, err = db.Exec(context.Background(), query, data.Test1, data.Test2, data.Test3, data.Test4, data.Test5, data.Test6, data.Test7)if err != nil {http.Error(w, "插入数据失败: "+err.Error(), http.StatusInternalServerError)return}SendJSONResponse(w, true, http.StatusOK, "成功", "数据已插入")
}// @Summary      查询指定表名的全部数据
// @Description  根据提供的表名查询数据库中的所有数据。
// @Tags         tags1
// @Produce      json
// @Param        tableName query string true  "要查询的数据库表名" default(table1)
// @Router       /findTable [get]
func findTableNameHandler(w http.ResponseWriter, r *http.Request) {tableName := r.URL.Query().Get("tableName")if tableName == "" {http.Error(w, "tableName is empty", http.StatusBadRequest)return}// ✅ 安全校验表名(防止 SQL 注入)if !isValidTableName(tableName) {http.Error(w, "invalid table name", http.StatusBadRequest)return}// ✅ 使用参数化方式拼接表名(仅限对象名,如表、字段)query := fmt.Sprintf("SELECT * FROM %s", tableName)rows, err := db.Query(context.Background(), query)if err != nil {http.Error(w, "查询失败: "+err.Error(), http.StatusInternalServerError)return}defer rows.Close()// ✅ 使用 pgx 内置工具自动转为 []map[string]interface{}data, err := pgx.CollectRows(rows, pgx.RowToMap)if err != nil {http.Error(w, "解析数据失败: "+err.Error(), http.StatusInternalServerError)return}SendJSONResponse(w, true, http.StatusOK, "成功", data)
}// 安全校验表名(防止 SQL 注入)
func isValidTableName(name string) bool {// 只允许字母、数字、下划线,且不能以数字开头matched, _ := regexp.MatchString(`^[a-zA-Z_][a-zA-Z0-9_]*$`, name)return matched
}

二、遇到的问题
swagger访问地址是8081端口,而点击try it out 调用的是8080端口,所以需要配置允许跨域的设置,代码注释中已写明。
swagger生效需要每次修改注释后执行 swag init,重写生成swagger文档再使用go run . 启动服务。
执行swag init如果出错,大概率是环境变量路径问题。
功能多了,代码就越来越乱了,后续再整理。

http://www.dtcms.com/a/338112.html

相关文章:

  • Three.js 坐标系系统与单位理解教程
  • 安装pnpm i -D @types/wechat-miniprogram报错,版本不匹配
  • 使用 Zed + Qwen Code 搭建轻量化 AI 编程 IDE
  • 【CF】Day129——杂题 (状压DP + 图论 | 贪心 + 数论 + 构造 | 构造 + 贪心 | 构造 + 模拟)
  • Python装饰器:从入门到精通
  • 【STM32】SPI 与 Flash 笔记
  • 【深度长文】Anthropic发布Prompt Engineering全新指南
  • 启发式合并
  • 1、代码相关优化建议
  • 数据分析进阶——解读文本分析模型【附全文阅读】
  • 第十六届蓝桥杯青少组C++省赛[2025.8.10]第二部分编程题(5、环形取硬币游戏)
  • 虚幻基础:动作时间窗
  • Kafka文件存储机制
  • 录音转文字,如何做到“快、准、狠“多格式通吃?
  • 自学中医笔记(二)
  • 大模型对齐算法(四): DAPO,VAPO,GMPO,GSPO, CISPO,GFPO
  • 如何平衡电竞酒店和高校宿舍对AI云电竞游戏盒子的不同需求?
  • 【Python】Python 多进程与多线程:从原理到实践
  • NVIDIA CWE 2025 上海直击:从 GPU 集群到 NeMo 2.0,企业 AI 智能化的加速引擎
  • 软件定义汽车---创新与差异化之路
  • C/C++ 中 str、str、*str 在指针语境下的具体含义(以 char* str 为例):
  • 深化中东战略承诺,联想集团宣布在利雅得设区域总部
  • wait / notify、单例模式
  • 【深度学习基础】PyTorch Tensor生成方式及复制方法详解
  • 【每日一题】Day 7
  • Linux——进程间、线程间的通信
  • 【C++】 using声明 与 using指示
  • 《彩色终端》诗解——ANSI 艺术解码(DeepSeek)
  • C++设计模式:建造者模式
  • 《若依》权限控制