不能并发的给 socket 发消息,竟然导致panic还退出应用,不仅仅是退出函数

panic: concurrent write to websocket connection
解决方案:
https://blog.csdn.net/qq_40374604/article/details/139141664

package main

import (
    "log"
    "net/http"
    "sync"

    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

// 定义一个结构体来包含WebSocket连接和互斥锁
type WebSocketConnection struct {
    Conn *websocket.Conn
    Lock sync.Mutex
}

func handleConnections(ws *websocket.Conn) {
    defer ws.Close()
    log.Println("Connection established")

    // 创建WebSocketConnection实例
    conn := &WebSocketConnection{
        Conn: ws,
        Lock: sync.Mutex{},
    }

    // Ping goroutine
    go func() {
        for {
            // 使用互斥锁来同步写操作
            conn.Lock()
            if err := ws.WriteMessage(websocket.PingMessage, nil); err != nil {
                log.Println("Failed to send Ping: ", err)
                return
            }
            conn.Unlock()

            time.Sleep(10 * time.Second)
        }
    }()

    // 消息处理goroutine
    go func() {
        // 这里可以处理接收到的消息等
        // ...
    }()

    // 这里可以添加更多的goroutine来处理不同的任务
    // ...
}

func main() {
    http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
        ws, err := upgrader.Upgrade(w, r, nil)
        if err != nil {
            log.Println("upgrade:", err)
            return
        }

        go handleConnections(ws)
    })

    log.Fatal(http.ListenAndServe(":8080", nil))
}

充值导致用户余额翻倍

原因:用户充值后,用gorm事务将充值订单存入订单数据表,同步修改用户余额表,因为页面要轮询检查用户是否完成扫码充值,在服务端发给微信服务器的接口里,做了存储充值记录(订单)和修改用户余额的动作,而服务端轮询接口里,一旦成功,也做了二次存入订单以及修改用户余额的动作,但是后者,存入订单动作做了唯一性判断,即firstorcreate,因为订单可以做唯一性判断,但是余额只有一条,通过不断修改(update)来实现,没有办法通过唯一性来判断是否重复修改。所以,在策略上,应该是当判断订单已经存在后,则不修改用户余额了,因为存订单和修改余额是用了事务,所以说明在之前存订单的时候,余额是修改成功的,否则订单也就是发生存入错误。

    // 新建充值
    result := tx.FirstOrCreate(&wxuserrecharge, WxUserRecharge{OutTradeNo: outtradeno, TransactionId: transactionid})
    err = result.Error
    if err != nil {
        tx.Rollback()
        return err
    }

    // 如果没找到,就创建一个新纪录
    // result := db.FirstOrCreate(&user, User{Name: "non_existing"})
    // SQL: INSERT INTO "users" (name) VALUES ("non_existing");
    // user -> User{ID: 112, Name: "non_existing"}
    // result.RowsAffected // => 1 (record created)
    // 如果已经存在这个充值订单,则不更新后面的用户余额了,说明之前已经更新过了。
    if result.RowsAffected == 0 {
        tx.Rollback() // 这一句如果漏了,就会发生锁库
        return err
    }

    // 新建或更新余额
    var wxuserbalance WxUserBalance
    //没有查到则新增一条余额
    err = tx.FirstOrCreate(&wxuserbalance, WxUserBalance{UserID: uid}).Error
    if err != nil {
        tx.Rollback()
        return err
    }
    newamount := wxuserbalance.Amount + amount
    rowsAffected := tx.Model(&wxuserbalance).Update("amount", newamount).RowsAffected
    if rowsAffected == 0 {
        tx.Rollback()
        return err
    }
    return tx.Commit().Error
作者:秦晓川  创建时间:2025-02-09 16:21
最后编辑:秦晓川  更新时间:2025-03-06 09:52
上一篇:
下一篇: