找到
67
篇与
框架教程
相关的结果
-
GinCdn内容分发系统V1.0.8更新内容 GinCdn内容分发系统 GinCdn是一款基于Go语言Gin框架自研的轻量高效内容分发系统,专为中小型企业/个人搭建CDN打造,采用主控+边缘节点分布式架构,实现智能调度、高效缓存、精准监控的一体化解决方案。 无需复杂命令行,小白也能轻松上手。 gincdn透明.png图片 版本更新日志 V1.0.8(2026.03.29) 完善节点删除逻辑 完善线路组删除逻辑 完善节点区域删除逻辑 修复多域名显示问题 修复SSL证书获取问题 美化多个界面显示样式 V1.0.7(2026.03.28) 新增支付宝实名配置界面 用户新增实名认证界面 新增网站配置xss过滤 V1.0.6(2026.03.26) 新增qrcode二维码生成 修复当面付余额充值转码 修复当面付套餐购买转码 修复当面付套餐续费转码 V1.0.5(2026.03.26) 新增用户注册动态配置功能 完善用户注册页动态调节 完善用户密码找回页 完善用户密码修改页 完善管理员密码修改页 修复邮箱配置功能 V1.0.4(2026.03.21) 新增端口四层转发功能 V1.0.3(2026.03.19) 新增错误页配置美化并同步 修复手机界面控制台菜单栏 V1.0.2(2026.03.16) 新增节点监控日志 新增节点监控配置 新增节点实时状态 新增节点通知配置 V1.0.1(2026.03.15) 新增离线节点差量同步 新增节点日志记录表 新增线路节点异步同步 新增当前在线节点获取 V1.0.0(2026.03.06) 支持阿里云DNS 支持多节点部署 支持彩虹易支付 支持套餐分类 支持套餐设置 支持邮箱发信 支持全局防火墙配置 支持全局Nginx配置 缓存、默认页、错误页配置 支持角色折扣功能 支持证书上传 支持区域线路解析 支持站点接入 支持同步配置 官方网站:www.gincdn.cn 正版授权官网:auth.shuha.cn -
GinCdn内容分发系统V1.0.4更新内容 GinCdn内容分发系统 GinCdn是一款基于Go语言Gin框架自研的轻量高效内容分发系统,专为中小型企业/个人搭建CDN打造。依托Go高性能特性,采用主控+边缘节点分布式架构,实现智能调度、高效缓存、精准监控的一体化解决方案。 无需复杂命令行,小白也能轻松上手。 1.jpg图片 2.jpg图片 3.jpg图片 4.jpg图片 5.jpg图片 6.jpg图片 版本更新日志 V1.0.4(2026.03.21) 新增端口四层转发功能 V1.0.3(2026.03.19) 新增错误页配置美化并同步 修复手机界面控制台菜单栏 V1.0.2(2026.03.16) 新增节点监控日志 新增节点监控配置 新增节点实时状态 新增节点通知配置 V1.0.1(2026.03.15) 新增离线节点差量同步 新增节点日志记录表 新增线路节点异步同步 新增当前在线节点获取 V1.0.0(2026.03.06) 支持阿里云DNS 支持多节点部署 支持彩虹易支付 支持套餐分类 支持套餐设置 支持邮箱发信 支持全局防火墙配置 支持全局Nginx配置 缓存、默认页、错误页配置 支持角色折扣功能 支持证书上传 支持区域线路解析 支持站点接入 支持同步配置 官方网站:www.gincdn.cn 正版授权官网:auth.shuha.cn -
BugKu WEB-计算器题目详解与解题思路 BugKu WEB-计算器题目详解与解题思路 计算器题目是BugKu平台上的一道经典WEB题目,主要考察前端限制绕过和基础HTML知识。这道题目看似简单,却蕴含着WEB安全的基础概念。本文将详细介绍这道题目的解题思路、多种解法以及相关的知识点扩展。 题目描述 题目链接通常为:http://123.206.87.240:8002/yanzhengma/(不同时期可能有变化) 题目界面显示一个简单的加法计算题,例如"59 + 72 = ?",用户需要在输入框中填写正确答案并点击验证按钮。然而,尝试输入时会发现输入框只能输入一个数字,无法输入完整的两位数答案。 解题思路分析 1. 观察题目限制 首先尝试直接输入计算结果,会发现输入框只能接受一位数字的输入,这显然无法满足计算题的需求(因为59+72=131是三位数)。 2. 查看页面源代码 按F12打开开发者工具,查看输入框的HTML代码,会发现类似以下结构: <input type="text" class="input" maxlength="1"/>计算器1.png图片 关键点在于maxlength="1"属性,它限制了输入框只能输入1个字符。 计算器2.png图片 把maxlength="1"属性,改成了输入框能输入10个字符。 计算器3.png图片 这样就可以正常输入正确的验证码了,成功获取到flag 3. 突破前端限制 既然问题是前端限制导致的,我们可以通过修改HTML属性来突破这个限制: 在开发者工具中找到这个input元素 将maxlength="1"修改为更大的值,如maxlength="10" 然后在输入框中输入正确答案(如59+72=131) 点击验证按钮获取flag 多种解题方法 方法一:直接修改HTML属性(推荐) 打开题目页面 按F12打开开发者工具 找到输入框对应的HTML代码 修改maxlength属性值为10 输入正确答案并提交 方法二:禁用JavaScript验证 有些情况下,验证逻辑是通过JavaScript实现的,可以尝试: 在开发者工具中禁用JavaScript 然后直接输入答案提交 方法三:使用Burp Suite拦截修改 开启Burp Suite拦截功能 在页面输入任意数字并提交 在Burp中拦截请求,修改提交的参数值为正确答案 放行请求获取响应 题目考察点 这道题目主要考察以下几个知识点: 前端限制的不可靠性:前端验证可以被轻易绕过,重要的验证必须在后端进行 HTML基础属性:maxlength属性的作用与修改方法 开发者工具的使用:如何查看和修改页面元素 WEB安全基础:理解客户端与服务器端验证的区别 知识点扩展 1. 前端限制的常见形式 maxlength:限制输入长度 disabled:禁用输入 readonly:只读属性 JavaScript事件监听:如oninput, onchange等 2. 如何绕过各种前端限制 限制类型绕过方法maxlength修改HTML属性或直接发送请求disabled/readonly移除属性或使用开发者工具启用JavaScript验证禁用JS或修改验证函数输入类型限制修改type属性或拦截请求修改3. 安全开发建议 永远不要依赖前端验证作为唯一的安全措施 重要业务逻辑必须在服务器端进行验证 对用户输入进行严格的过滤和验证 使用CSRF令牌防止跨站请求伪造 常见问题解答 Q: 为什么修改了maxlength还是无法提交? A: 可能还有其他限制,如JavaScript验证,需要一并处理 Q: 题目中的计算题会变化吗? A: 通常固定,但不同实例可能有不同题目,需要根据实际情况计算 Q: 有没有更简单的方法不用开发者工具? A: 可以尝试右键查看源代码,复制整个HTML到本地修改后打开 总结 BugKu的计算器题目通过一个简单的加法计算场景,巧妙地展示了前端限制的不可靠性。通过这道题目,我们学习到了: 如何查看和修改页面元素属性 前端限制的多种绕过方法 WEB安全中客户端与服务器端验证的重要性 开发者工具的基本使用技巧 这道题目虽然简单,但却是WEB安全学习的良好起点,理解这些基础概念对于后续学习更复杂的WEB安全知识至关重要。 -
彩虹易支付Go语言Gin框架对接指南:高效支付系统实现方案 彩虹易支付Go语言Gin框架对接指南:高效支付系统实现方案 引言 在当今数字化时代,支付接口对接是各类应用开发中不可或缺的环节。对于Go语言开发者而言,如何高效地对接第三方支付接口是一个常见的挑战。本文将详细介绍如何使用Go语言的Gin框架对接彩虹易支付接口,提供完整的订单管理、状态同步和超时处理解决方案。 彩虹易支付简介 彩虹易支付是一款开源的支付系统,支持多种支付渠道(如支付宝、微信支付、QQ支付等),提供简单易用的API接口,适合个人和中小企业快速集成支付功能。其核心优势包括: 支持多种支付方式 提供异步通知和主动查询两种订单状态同步机制 完善的签名验证机制保障交易安全 简洁明了的API文档便于开发 go.jpg图片 系统架构设计 我们的支付系统采用分层架构设计,主要包含以下模块: 模型层:定义支付配置和订单数据结构 控制器层:处理支付请求、回调通知和订单监控 服务层:封装第三方支付接口调用逻辑 工具层:提供签名生成、订单号生成等基础工具 核心依赖包 以下是实现该支付系统所需的主要Go依赖包: import ( "github.com/gin-gonic/gin" // Web框架 "github.com/go-gorm/gorm" // ORM库 "github.com/robfig/cron/v3" // 定时任务库 "github.com/google/uuid" // UUID生成 "crypto/md5" // 签名加密 "encoding/hex" // 进制转换 "net/http" // HTTP客户端 "encoding/json" // JSON处理 "strconv" // 字符串转换 "time" // 时间处理 "math/rand" // 随机数生成 "strings" // 字符串操作 "sort" // 排序 "math" // 数学运算 )数据库模型设计 我们需要定义两个核心模型:支付配置和支付订单。 // 支付配置模型 type PayConfig struct { ID uint `gorm:"primaryKey;comment:ID" json:"id"` Pid uint `gorm:"comment:商户ID" json:"pid"` PayKey string `gorm:"size:128;comment:商户密钥" json:"payKey"` PayUrl string `gorm:"size:64;接口地址" json:"payUrl"` NotifyUrl string `gorm:"comment:异步通知地址" json:"notifyUrl"` ReturnUrl string `gorm:"comment:跳转通知地址" json:"returnUrl"` CreateTime utils.HTime `gorm:"comment:创建时间" json:"createTime"` UpdateTime utils.HTime `gorm:"comment:更新时间;autoUpdateTime" json:"updateTime"` } // 支付订单模型 type PayOrder struct { ID uint `gorm:"primaryKey;comment:ID" json:"id"` TradeNo string `gorm:"size:64;comment:订单号" json:"tradeNo"` Type string `gorm:"size:16;comment:支付方式" json:"type"` Name string `gorm:"size:127;comment:商品名称" json:"name"` Money float64 `gorm:"type:decimal(10,2);comment:商品金额" json:"money"` Username string `gorm:"comment:用户账号" json:"username"` Status int `gorm:"default:1;comment:支付状态(1->未支付,2->已支付,3->已取消)" json:"status"` CreateTime utils.HTime `gorm:"comment:创建时间" json:"createTime"` PayTime utils.HTime `gorm:"comment:支付时间" json:"payTime"` }支付流程实现 整个支付流程包括订单创建、支付请求、异步通知和订单状态监控四个主要环节。 1. 创建支付订单 // 创建支付订单 func CreatePayOrder(db *gorm.DB, payConfig *model.PayConfig) gin.HandlerFunc { return func(c *gin.Context) { // 1. 获取请求参数 var req struct { Type string `form:"type" binding:"required,oneof=alipay wxpay qqpay"` Name string `form:"name" binding:"required"` Money float64 `form:"money" binding:"gt=0"` } if err := c.ShouldBind(&req); err != nil { result.Failed(c, int(result.ApiCode.QueShaoCanShu), "参数错误:"+err.Error()) return } // 2. 从JWT获取用户名 username, exists := c.Get("username") if !exists { result.Failed(c, int(result.ApiCode.Failed), "未登录或登录已过期") return } // 3. 验证用户是否存在 var user model.User if err := db.Where("username = ?", username).First(&user).Error; err != nil { result.Failed(c, int(result.ApiCode.UserNotExist), "用户不存在") return } // 4. 开启数据库事务 err := db.Transaction(func(tx *gorm.DB) error { // 生成临时本地订单号 tempLocalTradeNo := generatePAYTradeNo() // 准备第三方支付参数 paymentParams := map[string]string{ "pid": strconv.Itoa(int(payConfig.Pid)), "type": req.Type, "out_trade_no": tempLocalTradeNo, "notify_url": payConfig.NotifyUrl, "return_url": payConfig.ReturnUrl, "name": req.Name, "money": fmt.Sprintf("%.2f", req.Money), "clientip": c.ClientIP(), "device": "pc", "param": "", } // 生成签名 sign := generatePaymentSign(paymentParams, payConfig.PayKey) paymentParams["sign"] = sign paymentParams["sign_type"] = "MD5" // 调用第三方支付接口 payResult, innerErr := sendPaymentRequest(paymentParams, payConfig.PayUrl+"/mapi.php") if innerErr != nil { return fmt.Errorf("调用第三方接口失败: %w", innerErr) } // 检查第三方接口返回状态 code, ok := payResult["code"].(float64) if !ok || code != 1 { msg, _ := payResult["msg"].(string) return fmt.Errorf("第三方创建订单失败: code=%v, msg=%s", code, msg) } // 提取第三方生成的订单号 thirdTradeNo, ok := payResult["trade_no"].(string) if !ok || thirdTradeNo == "" { return fmt.Errorf("第三方未返回有效订单号,响应: %v", payResult) } // 创建本地订单 order = model.PayOrder{ TradeNo: thirdTradeNo, Type: req.Type, Name: req.Name, Money: req.Money, Username: user.Username, CreateTime: utils.HTime{Time: time.Now()}, Status: constant.OrderNotPay, } // 保存本地订单到数据库 if err := tx.Create(&order).Error; err != nil { return fmt.Errorf("本地订单保存失败: %w", err) } return nil }) // 处理事务结果 if err != nil { result.Failed(c, int(result.ApiCode.Failed), "创建支付订单失败: "+err.Error()) return } // 返回成功响应 result.Success(c, gin.H{ "order": order, "payResult": payResult, "msg": "订单创建成功,请完成支付", }) } }2. 处理异步通知 // 处理支付结果通知 func NotifyPayOrder(db *gorm.DB, payConfig *model.PayConfig) gin.HandlerFunc { return func(c *gin.Context) { // 获取所有GET参数 params := make(map[string]string) for key, values := range c.Request.URL.Query() { if len(values) > 0 { params[key] = values[0] } } // 获取第三方订单号 thirdTradeNo := params["trade_no"] if thirdTradeNo == "" { c.String(http.StatusBadRequest, "missing trade_no") return } // 验证签名 sign := params["sign"] if sign == "" { c.String(http.StatusBadRequest, "missing sign") return } // 验证签名是否正确 if !verifyPaymentSign(params, payConfig.PayKey, sign) { c.String(http.StatusBadRequest, "invalid sign") return } // 验证支付状态 tradeStatus := params["trade_status"] if tradeStatus != "TRADE_SUCCESS" { c.String(http.StatusOK, "success") return } // 开启数据库事务 err := db.Transaction(func(tx *gorm.DB) error { // 用第三方订单号查询本地订单 var order model.PayOrder if err := tx.Where("trade_no = ?", thirdTradeNo).First(&order).Error; err != nil { return fmt.Errorf("查询本地订单失败: %w", err) } // 检查订单状态,防止重复处理 if order.Status == constant.OrderPaySuccess { return nil } // 验证金额 orderMoney := order.Money notifyMoney, err := strconv.ParseFloat(params["money"], 64) if err != nil { return fmt.Errorf("解析通知金额失败: %v", err) } // 使用容差值比较两个浮点数 const epsilon = 0.0001 if math.Abs(orderMoney-notifyMoney) > epsilon { return fmt.Errorf("金额不匹配: 订单金额=%.2f, 通知金额=%.2f", orderMoney, notifyMoney) } // 更新订单状态为已支付 order.Status = constant.OrderPaySuccess order.PayTime = utils.HTime{Time: time.Now()} // 更新订单信息 return tx.Save(&order).Error }) if err != nil { log.Printf("处理支付通知失败: %v", err) c.String(http.StatusInternalServerError, "处理通知失败") return } // 返回success表示接收成功 c.String(http.StatusOK, "success") } }3. 订单状态主动查询 // 查询订单状态 func QueryPayOrder(payConfig *model.PayConfig, tradeNo string) (map[string]interface{}, error) { // 准备查询参数 queryParams := url.Values{} queryParams.Add("act", "order") queryParams.Add("pid", strconv.Itoa(int(payConfig.Pid))) queryParams.Add("key", payConfig.PayKey) queryParams.Add("trade_no", tradeNo) // 使用第三方订单号查询 // 构建完整URL queryUrl := fmt.Sprintf("%s/api.php?%s", payConfig.PayUrl, queryParams.Encode()) // 发送GET请求 client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequest("GET", queryUrl, nil) if err != nil { return nil, fmt.Errorf("创建查询请求失败: %w", err) } resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("发送查询请求失败: %w", err) } defer resp.Body.Close() // 解析JSON响应 var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("解析查询结果失败: %w", err) } // 验证响应签名 if sign, ok := result["sign"].(string); ok { verifyParams := make(map[string]string) for k, v := range result { if k != "sign" { if strVal, ok := v.(string); ok { verifyParams[k] = strVal } } } verifySign := generatePaymentSign(verifyParams, payConfig.PayKey) if verifySign != sign { return nil, fmt.Errorf("响应签名验证失败") } } // 检查API返回状态码 code, ok := result["code"].(float64) if !ok { return nil, fmt.Errorf("响应中缺少code字段: %v", result) } if code != 1 { msg, _ := result["msg"].(string) if msg == "" { msg = "未知错误" } return nil, fmt.Errorf("API调用失败: code=%v, msg=%s", code, msg) } // 确保包含支付状态字段 if _, ok := result["status"]; !ok { return nil, fmt.Errorf("响应中缺少status字段: %v", result) } return result, nil }4. 订单状态监控任务 // 启动支付订单监控任务 func StartPayOrderMonitor(payConfig *model.PayConfig) { c := cron.New(cron.WithSeconds()) // 每5秒执行一次 _, err := c.AddFunc("@every 5s", func() { monitorUnpaidOrders(payConfig) }) if err != nil { global.Log.Errorf("启动支付监控任务失败: %v", err) return } c.Start() global.Log.Infof("支付订单监控任务已启动(每5秒执行一次,检查所有未支付订单)") } // 监控未支付订单 func monitorUnpaidOrders(payConfig *model.PayConfig) { global.Log.Infof("开始检查未支付订单...") var unpaidOrders []model.PayOrder // 查询所有未支付订单 if err := core.Db.Where("status = ?", constant.OrderNotPay).Find(&unpaidOrders).Error; err != nil { global.Log.Errorf("查询未支付订单失败: %v", err) return } global.Log.Infof("发现 %d 个未支付订单,开始同步状态...", len(unpaidOrders)) for _, order := range unpaidOrders { global.Log.Debugf("开始同步订单状态: 订单号=%s, 创建时间=%v", order.TradeNo, order.CreateTime) // 计算订单创建时间是否超过15分钟 fifteenMinutesAgo := time.Now().Add(-15 * time.Minute) if order.CreateTime.Time.Before(fifteenMinutesAgo) { global.Log.Infof("订单超时未支付(>15分钟),标记为取消: %s", order.TradeNo) cancelOrder(order.TradeNo) continue } // 跳过刚创建的订单(30秒内) if time.Since(order.CreateTime.Time) < 30*time.Second { global.Log.Debugf("订单创建时间过近(<30秒),跳过本次查询: %s", order.TradeNo) continue } // 查询第三方支付状态 payResult, err := QueryPayOrder(payConfig, order.TradeNo) if err != nil { if strings.Contains(err.Error(), "订单号不存在") { global.Log.Warnf("第三方订单不存在,标记为取消: %s", order.TradeNo) cancelOrder(order.TradeNo) } else { global.Log.Errorf("查询订单状态失败(将重试): 订单号=%s, 错误=%v", order.TradeNo, err) } continue } // 解析第三方支付状态 statusVal, statusExists := payResult["status"] if !statusExists { global.Log.Warnf("响应中缺少status字段: 订单号=%s", order.TradeNo) continue } // 尝试多种类型转换 var status int switch v := statusVal.(type) { case float64: status = int(v) case string: var err error status, err = strconv.Atoi(v) if err != nil { global.Log.Warnf("status字段格式错误: 订单号=%s, 值=%v", order.TradeNo, v) continue } case int: status = v case int64: status = int(v) default: global.Log.Warnf("status字段类型不支持: 订单号=%s, 类型=%T", order.TradeNo, v) continue } // 根据状态更新本地订单 if status == 1 { global.Log.Infof("第三方确认已支付: 订单号=%s", order.TradeNo) updateOrderToPaid(order.TradeNo, payResult) } else { global.Log.Debugf("第三方确认未支付: 订单号=%s, 状态=%d", order.TradeNo, status) } } global.Log.Infof("未支付订单状态同步完成") }签名验证机制 签名验证是支付系统安全的关键环节,我们需要实现严格的签名生成和验证算法。 // 生成支付签名 func generatePaymentSign(params map[string]string, secretKey string) string { // 1. 过滤空值和排除sign、sign_type var keys []string for k, v := range params { if k != "sign" && k != "sign_type" && v != "" { keys = append(keys, k) } } // 2. 按参数名ASCII排序 sort.Strings(keys) // 3. 拼接参数为key=value&key=value格式 var paramStr string for i, k := range keys { if i > 0 { paramStr += "&" } paramStr += fmt.Sprintf("%s=%s", k, params[k]) } // 4. 拼接密钥并MD5加密 paramStr += secretKey h := md5.New() h.Write([]byte(paramStr)) return hex.EncodeToString(h.Sum(nil)) } // 验证支付通知签名 func verifyPaymentSign(params map[string]string, secretKey string, receivedSign string) bool { // 复制参数,排除sign字段 paramsToSign := make(map[string]string) for k, v := range params { if k != "sign" { paramsToSign[k] = v } } // 生成签名 generatedSign := generatePaymentSign(paramsToSign, secretKey) // 比较签名 return generatedSign == receivedSign }系统部署与配置 配置数据库连接:确保GORM正确连接到MySQL数据库 初始化支付配置:在数据库中插入彩虹易支付的商户信息 启动服务:运行Gin应用,监听HTTP请求 配置定时任务:启动订单监控任务,确保订单状态及时同步 优化与扩展建议 限流与熔断:添加对第三方接口的限流和熔断机制,防止频繁请求导致被封 批量处理:对于大量未支付订单,采用分批处理策略,避免内存溢出 告警机制:添加异常告警,当出现大量签名验证失败或订单状态异常时及时通知管理员 数据统计:添加支付数据统计功能,方便业务分析 总结 通过本文的实现方案,我们成功完成了彩虹易支付与Go语言Gin框架的对接。该方案充分利用了Gin框架的高性能和灵活性,结合彩虹易支付的强大功能,为开发者提供了一个完整、高效的支付系统解决方案。系统涵盖了订单创建、支付、异步通知和主动查询等核心环节,并通过定时任务确保订单状态的最终一致性。 这个方案不仅适用于彩虹易支付,也可以作为其他支付接口对接的参考框架,开发者可以根据具体需求进行相应的调整和扩展。 -
Gin框架对接彩虹易支付接口完整实现方案 Gin框架对接彩虹易支付接口完整实现方案 一、基础配置 首先创建支付配置结构体,用于存储商户信息: type PayConfig struct { Pid int // 商户ID Key string // 商户密钥 NotifyUrl string // 异步通知地址 ReturnUrl string // 跳转通知地址 ApiUrl string // 接口地址 }go.jpg图片 二、支付请求实现 1. 页面跳转支付实现 func PageJumpPay(c *gin.Context) { var req struct { PayType string `json:"pay_type"` // alipay/wxpay/qqpay Amount float64 `json:"amount" binding:"required"` OrderNo string `json:"order_no" binding:"required"` Goods string `json:"goods" binding:"required"` } if err := c.ShouldBind(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } params := map[string]interface{}{ "pid": config.PayCfg.Pid, "type": req.PayType, "out_trade_no": req.OrderNo, "notify_url": config.PayCfg.NotifyUrl, "return_url": config.PayCfg.ReturnUrl, "name": req.Goods, "money": fmt.Sprintf("%.2f", req.Amount), } sign := GenerateSign(params) params["sign"] = sign params["sign_type"] = "MD5" // 构建form表单自动提交 html := `<html><body><form id="payForm" action="https://pay.javait.cn/submit.php" method="post">` for k, v := range params { html += fmt.Sprintf(`<input type="hidden" name="%s" value="%v" />`, k, v) } html += `</form><script>document.getElementById('payForm').submit();</script></body></html>` c.Data(200, "text/html; charset=utf-8", []byte(html)) }2. API接口支付实现 func ApiPay(c *gin.Context) { var req struct { PayType string `json:"pay_type" binding:"required"` Amount float64 `json:"amount" binding:"required"` OrderNo string `json:"order_no" binding:"required"` Goods string `json:"goods" binding:"required"` ClientIP string `json:"client_ip" binding:"required"` Device string `json:"device"` } if err := c.ShouldBind(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } params := map[string]interface{}{ "pid": config.PayCfg.Pid, "type": req.PayType, "out_trade_no": req.OrderNo, "notify_url": config.PayCfg.NotifyUrl, "name": req.Goods, "money": fmt.Sprintf("%.2f", req.Amount), "clientip": req.ClientIP, "device": req.Device, } sign := GenerateSign(params) params["sign"] = sign params["sign_type"] = "MD5" client := resty.New() resp, err := client.R(). SetHeader("Content-Type", "application/x-www-form-urlencoded"). SetFormData(convertParams(params)). Post("https://pay.javait.cn/mapi.php") if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } var result map[string]interface{} if err := json.Unmarshal(resp.Body(), &result); err != nil { c.JSON(500, gin.H{"error": "parse response error"}) return } // 验证返回签名 if !VerifyResponseSign(result) { c.JSON(500, gin.H{"error": "sign verify failed"}) return } c.JSON(200, result) }三、签名与验证实现 1. 签名生成函数 func GenerateSign(params map[string]interface{}) string { // 过滤空值和签名相关字段 filtered := make(map[string]string) for k, v := range params { if k == "sign" || k == "sign_type" { continue } if val, ok := v.(string); ok && val == "" { continue } filtered[k] = fmt.Sprintf("%v", v) } // 按key排序 keys := make([]string, 0, len(filtered)) for k := range filtered { keys = append(keys, k) } sort.Strings(keys) // 拼接字符串 var signStr string for i, k := range keys { if i > 0 { signStr += "&" } signStr += k + "=" + filtered[k] } signStr += config.PayCfg.Key // MD5加密 h := md5.New() h.Write([]byte(signStr)) return hex.EncodeToString(h.Sum(nil)) }2. 响应签名验证 func VerifyResponseSign(data map[string]interface{}) bool { sign, ok := data["sign"].(string) if !ok { return false } // 复制一份数据并移除sign字段 params := make(map[string]interface{}) for k, v := range data { if k != "sign" { params[k] = v } } // 重新生成签名 calculatedSign := GenerateSign(params) return calculatedSign == sign }四、支付结果通知处理 1. 异步通知处理 func NotifyHandler(c *gin.Context) { params := make(map[string]interface{}) if err := c.ShouldBind(¶ms); err != nil { c.String(400, "fail") return } // 验证签名 if !VerifyResponseSign(params) { c.String(400, "fail") return } // 验证支付状态 status, ok := params["trade_status"].(string) if !ok || status != "TRADE_SUCCESS" { c.String(400, "fail") return } // 处理业务逻辑 orderNo := params["out_trade_no"].(string) amount := params["money"].(string) // TODO: 更新订单状态 c.String(200, "success") }2. 跳转通知处理 func ReturnHandler(c *gin.Context) { params := make(map[string]interface{}) if err := c.ShouldBind(¶ms); err != nil { c.Redirect(302, "/error?msg=参数错误") return } // 验证签名 if !VerifyResponseSign(params) { c.Redirect(302, "/error?msg=签名验证失败") return } // 验证支付状态 status, ok := params["trade_status"].(string) if !ok || status != "TRADE_SUCCESS" { c.Redirect(302, "/error?msg=支付未成功") return } // 跳转到支付成功页面 orderNo := params["out_trade_no"].(string) c.Redirect(302, "/pay/success?order_no="+orderNo) }五、辅助函数 // 转换参数为字符串map func convertParams(params map[string]interface{}) map[string]string { result := make(map[string]string) for k, v := range params { result[k] = fmt.Sprintf("%v", v) } return result } // 获取客户端IP func GetClientIP(c *gin.Context) string { ip := c.Request.Header.Get("X-Forwarded-For") if ip == "" { ip = c.Request.RemoteAddr } return strings.Split(ip, ":") }六、路由配置 func SetupPayRoutes(r *gin.Engine) { payGroup := r.Group("/pay") { payGroup.POST("/page", PageJumpPay) // 页面跳转支付 payGroup.POST("/api", ApiPay) // API接口支付 payGroup.POST("/notify", NotifyHandler) // 异步通知 payGroup.GET("/return", ReturnHandler) // 跳转通知 } }七、使用示例 1. 发起页面跳转支付 // 前端调用示例 POST /pay/page Content-Type: application/json { "pay_type": "alipay", "amount": 100.00, "order_no": "ORDER123456", "goods": "VIP会员" }2. 发起API支付 // 前端调用示例 POST /pay/api Content-Type: application/json { "pay_type": "wxpay", "amount": 100.00, "order_no": "ORDER123456", "goods": "VIP会员", "client_ip": "192.168.1.100", "device": "mobile" }3. 处理支付结果 支付平台会异步通知到配置的notify_url,并同步跳转到return_url,这两个URL分别对应我们实现的/pay/notify和/pay/return接口。 八、注意事项 安全性:确保商户密钥(Key)妥善保管,不要硬编码在代码中 幂等性:支付结果通知处理要实现幂等性,防止重复处理 日志记录:记录所有支付请求和通知的原始数据,便于排查问题 金额验证:处理通知时要验证金额与订单金额是否一致 超时设置:HTTP请求设置合理的超时时间 异常处理:做好各种异常情况的处理,如网络超时、签名错误等 以上实现完整覆盖了支付接口对接的所有环节,包括发起支付、签名验证、结果通知处理等关键功能,可以直接集成到您的Gin项目中。