init
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// BYTE 字节
|
||||
BYTE = 1 << (10 * iota)
|
||||
// KILOBYTE 千字节
|
||||
KILOBYTE
|
||||
// MEGABYTE 兆字节
|
||||
MEGABYTE
|
||||
// GIGABYTE 吉字节
|
||||
GIGABYTE
|
||||
// TERABYTE 太字节
|
||||
TERABYTE
|
||||
// PETABYTE 拍字节
|
||||
PETABYTE
|
||||
// EXABYTE 艾字节
|
||||
EXABYTE
|
||||
)
|
||||
|
||||
// Bytefmt returns a human-readable byte string of the form 10M, 12.5K, and so forth. The following units are available:
|
||||
// E: Exabyte
|
||||
// P: Petabyte
|
||||
// T: Terabyte
|
||||
// G: Gigabyte
|
||||
// M: Megabyte
|
||||
// K: Kilobyte
|
||||
// B: Byte
|
||||
// The unit that results in the smallest number greater than or equal to 1 is always chosen.
|
||||
func Bytefmt(bytes uint64) string {
|
||||
unit := ""
|
||||
value := float64(bytes)
|
||||
|
||||
switch {
|
||||
case bytes >= EXABYTE:
|
||||
unit = "E"
|
||||
value = value / EXABYTE
|
||||
case bytes >= PETABYTE:
|
||||
unit = "P"
|
||||
value = value / PETABYTE
|
||||
case bytes >= TERABYTE:
|
||||
unit = "T"
|
||||
value = value / TERABYTE
|
||||
case bytes >= GIGABYTE:
|
||||
unit = "G"
|
||||
value = value / GIGABYTE
|
||||
case bytes >= MEGABYTE:
|
||||
unit = "M"
|
||||
value = value / MEGABYTE
|
||||
case bytes >= KILOBYTE:
|
||||
unit = "K"
|
||||
value = value / KILOBYTE
|
||||
case bytes >= BYTE:
|
||||
unit = "B"
|
||||
case bytes == 0:
|
||||
return "0B"
|
||||
}
|
||||
|
||||
result := strconv.FormatFloat(value, 'f', 2, 64)
|
||||
result = strings.TrimSuffix(result, ".0")
|
||||
return result + unit
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func systemctlReplace(out string) (bool, error) {
|
||||
var (
|
||||
err error
|
||||
isReplace bool
|
||||
)
|
||||
if IsExists("/.dockerenv") && strings.Contains(out, "Failed to get D-Bus") {
|
||||
isReplace = true
|
||||
fmt.Println(Yellow("正在下载并替换适配的systemctl。。"))
|
||||
if err = ExecCommand("curl -L https://raw.githubusercontent.com/gdraheim/docker-systemctl-replacement/master/files/docker/systemctl.py -o /usr/bin/systemctl && chmod +x /usr/bin/systemctl"); err != nil {
|
||||
return isReplace, err
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
return isReplace, err
|
||||
}
|
||||
|
||||
func systemctlBase(name, operate string) (string, error) {
|
||||
out, err := exec.Command("bash", "-c", fmt.Sprintf("systemctl %s %s", operate, name)).CombinedOutput()
|
||||
if v, _ := systemctlReplace(string(out)); v {
|
||||
out, err = exec.Command("bash", "-c", fmt.Sprintf("systemctl %s %s", operate, name)).CombinedOutput()
|
||||
}
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
// SystemctlStart 服务启动
|
||||
func SystemctlStart(name string) {
|
||||
if _, err := systemctlBase(name, "start"); err != nil {
|
||||
fmt.Println(Red(fmt.Sprintf("启动%s失败!", name)))
|
||||
} else {
|
||||
fmt.Println(Green(fmt.Sprintf("启动%s成功!", name)))
|
||||
}
|
||||
}
|
||||
|
||||
// SystemctlStop 服务停止
|
||||
func SystemctlStop(name string) {
|
||||
if _, err := systemctlBase(name, "stop"); err != nil {
|
||||
fmt.Println(Red(fmt.Sprintf("停止%s失败!", name)))
|
||||
} else {
|
||||
fmt.Println(Green(fmt.Sprintf("停止%s成功!", name)))
|
||||
}
|
||||
}
|
||||
|
||||
// SystemctlRestart 服务重启
|
||||
func SystemctlRestart(name string) {
|
||||
if _, err := systemctlBase(name, "restart"); err != nil {
|
||||
fmt.Println(Red(fmt.Sprintf("重启%s失败!", name)))
|
||||
} else {
|
||||
fmt.Println(Green(fmt.Sprintf("重启%s成功!", name)))
|
||||
}
|
||||
}
|
||||
|
||||
// SystemctlEnable 服务设置开机自启
|
||||
func SystemctlEnable(name string) {
|
||||
if _, err := systemctlBase(name, "enable"); err != nil {
|
||||
fmt.Println(Red(fmt.Sprintf("设置%s开机自启失败!", name)))
|
||||
}
|
||||
}
|
||||
|
||||
// SystemctlStatus 服务状态查看
|
||||
func SystemctlStatus(name string) string {
|
||||
out, _ := systemctlBase(name, "status")
|
||||
return out
|
||||
}
|
||||
|
||||
// CheckCommandExists 检查命令是否存在
|
||||
func CheckCommandExists(command string) bool {
|
||||
if _, err := exec.LookPath(command); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// RunWebShell 运行网上的脚本
|
||||
func RunWebShell(webShellPath string) {
|
||||
if !strings.HasPrefix(webShellPath, "http") && !strings.HasPrefix(webShellPath, "https") {
|
||||
fmt.Printf("shell path must start with http or https!")
|
||||
return
|
||||
}
|
||||
resp, err := http.Get(webShellPath)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
installShell, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
ExecCommand(string(installShell))
|
||||
}
|
||||
|
||||
// ExecCommand 运行命令并实时查看运行结果
|
||||
func ExecCommand(command string) error {
|
||||
cmd := exec.Command("bash", "-c", command)
|
||||
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
stderr, _ := cmd.StderrPipe()
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
fmt.Println("Error:The command is err: ", err.Error())
|
||||
return err
|
||||
}
|
||||
ch := make(chan string, 100)
|
||||
stdoutScan := bufio.NewScanner(stdout)
|
||||
stderrScan := bufio.NewScanner(stderr)
|
||||
go func() {
|
||||
for stdoutScan.Scan() {
|
||||
line := stdoutScan.Text()
|
||||
ch <- line
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for stderrScan.Scan() {
|
||||
line := stderrScan.Text()
|
||||
ch <- line
|
||||
}
|
||||
}()
|
||||
var err error
|
||||
go func() {
|
||||
err = cmd.Wait()
|
||||
if err != nil && !strings.Contains(err.Error(), "exit status") {
|
||||
fmt.Println("wait:", err.Error())
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
for line := range ch {
|
||||
fmt.Println(line)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecCommandWithResult 运行命令并获取结果
|
||||
func ExecCommandWithResult(command string) string {
|
||||
out, err := exec.Command("bash", "-c", command).CombinedOutput()
|
||||
if strings.Contains(command, "systemctl") {
|
||||
if v, _ := systemctlReplace(string(out)); v {
|
||||
out, err = exec.Command("bash", "-c", command).CombinedOutput()
|
||||
}
|
||||
}
|
||||
if err != nil && !strings.Contains(err.Error(), "exit status") {
|
||||
fmt.Println("err: " + err.Error())
|
||||
return ""
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PortIsUse 判断端口是否占用
|
||||
func PortIsUse(port int) bool {
|
||||
_, tcpError := net.DialTimeout("tcp", fmt.Sprintf(":%d", port), time.Millisecond*50)
|
||||
udpAddr, _ := net.ResolveUDPAddr("udp4", fmt.Sprintf(":%d", port))
|
||||
udpConn, udpError := net.ListenUDP("udp", udpAddr)
|
||||
if udpConn != nil {
|
||||
defer udpConn.Close()
|
||||
}
|
||||
return tcpError == nil || udpError != nil
|
||||
}
|
||||
|
||||
// RandomPort 获取没占用的随机端口
|
||||
func RandomPort() int {
|
||||
for {
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
newPort := rand.Intn(65536)
|
||||
if !PortIsUse(newPort) {
|
||||
return newPort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsExists 检测指定路径文件或者文件夹是否存在
|
||||
func IsExists(path string) bool {
|
||||
_, err := os.Stat(path) //os.Stat获取文件信息
|
||||
if err != nil {
|
||||
if os.IsExist(err) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetLocalIP 获取本机ipv4地址
|
||||
func GetLocalIP() string {
|
||||
resp, err := http.Get("http://api.ipify.org")
|
||||
if err != nil {
|
||||
resp, _ = http.Get("http://icanhazip.com")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
s, _ := io.ReadAll(resp.Body)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// InstallPack 安装指定名字软件
|
||||
func InstallPack(name string) {
|
||||
if !CheckCommandExists(name) {
|
||||
if CheckCommandExists("yum") {
|
||||
ExecCommand("yum install -y " + name)
|
||||
} else if CheckCommandExists("apt-get") {
|
||||
ExecCommand("apt-get update")
|
||||
ExecCommand("apt-get install -y " + name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OpenPort 开通指定端口
|
||||
func OpenPort(port int) {
|
||||
if CheckCommandExists("firewall-cmd") {
|
||||
ExecCommand(fmt.Sprintf("firewall-cmd --zone=public --add-port=%d/tcp --add-port=%d/udp --permanent >/dev/null 2>&1", port, port))
|
||||
ExecCommand("firewall-cmd --reload >/dev/null 2>&1")
|
||||
} else {
|
||||
if len(ExecCommandWithResult(fmt.Sprintf(`iptables -nvL --line-number|grep -w "%d"`, port))) > 0 {
|
||||
return
|
||||
}
|
||||
ExecCommand(fmt.Sprintf("iptables -I INPUT -p tcp --dport %d -j ACCEPT", port))
|
||||
ExecCommand(fmt.Sprintf("iptables -I INPUT -p udp --dport %d -j ACCEPT", port))
|
||||
ExecCommand(fmt.Sprintf("iptables -I OUTPUT -p udp --sport %d -j ACCEPT", port))
|
||||
ExecCommand(fmt.Sprintf("iptables -I OUTPUT -p tcp --sport %d -j ACCEPT", port))
|
||||
}
|
||||
}
|
||||
|
||||
// Log 实时打印指定服务日志
|
||||
func Log(serviceName string, line int) {
|
||||
result, _ := LogChan(serviceName, "-n "+strconv.Itoa(line), make(chan byte))
|
||||
for line := range result {
|
||||
fmt.Println(line)
|
||||
}
|
||||
}
|
||||
|
||||
// LogChan 指定服务实时日志, 返回chan
|
||||
func LogChan(serviceName, param string, closeChan chan byte) (chan string, error) {
|
||||
cmd := exec.Command("bash", "-c", fmt.Sprintf("journalctl -f -u %s -o cat %s", serviceName, param))
|
||||
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
fmt.Println("Error:The command is err: ", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
ch := make(chan string, 100)
|
||||
stdoutScan := bufio.NewScanner(stdout)
|
||||
go func() {
|
||||
for stdoutScan.Scan() {
|
||||
select {
|
||||
case <-closeChan:
|
||||
stdout.Close()
|
||||
return
|
||||
default:
|
||||
ch <- stdoutScan.Text()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch, nil
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/eiannone/keyboard"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// RED 红色
|
||||
RED = "\033[31m"
|
||||
// GREEN 绿色
|
||||
GREEN = "\033[32m"
|
||||
// YELLOW 黄色
|
||||
YELLOW = "\033[33m"
|
||||
// BLUE 蓝色
|
||||
BLUE = "\033[34m"
|
||||
// FUCHSIA 紫红色
|
||||
FUCHSIA = "\033[35m"
|
||||
// CYAN 青色
|
||||
CYAN = "\033[36m"
|
||||
// WHITE 白色
|
||||
WHITE = "\033[37m"
|
||||
// RESET 重置颜色
|
||||
RESET = "\033[0m"
|
||||
// LETTER 大小写英文字母常量
|
||||
LETTER = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
// DIGITS 数字常量
|
||||
DIGITS = "0123456789"
|
||||
// SPECIALS 特殊字符常量
|
||||
SPECIALS = "~=+%^*/()[]{}/!@#$?|"
|
||||
// ALL 全部字符常量
|
||||
ALL = LETTER + DIGITS + SPECIALS
|
||||
)
|
||||
|
||||
// IsInteger 判断字符串是否为整数
|
||||
func IsInteger(input string) bool {
|
||||
_, err := strconv.Atoi(input)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// RandString 随机字符串
|
||||
func RandString(length int, source string) string {
|
||||
var runes = []rune(source)
|
||||
b := make([]rune, length)
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for i := range b {
|
||||
b[i] = runes[rand.Intn(len(runes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// VerifyEmailFormat 邮箱验证
|
||||
func VerifyEmailFormat(email string) bool {
|
||||
pattern := `^[0-9a-z][_.0-9a-z-]{0,31}@([0-9a-z][0-9a-z-]{0,30}[0-9a-z]\.){1,4}[a-z]{2,4}$`
|
||||
reg := regexp.MustCompile(pattern)
|
||||
return reg.MatchString(email)
|
||||
}
|
||||
|
||||
func getChar(str string) string {
|
||||
err := keyboard.Open()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer keyboard.Close()
|
||||
fmt.Print(str)
|
||||
char, _, _ := keyboard.GetKey()
|
||||
fmt.Printf("%c\n", char)
|
||||
if char == 0 {
|
||||
return ""
|
||||
}
|
||||
return string(char)
|
||||
}
|
||||
|
||||
// LoopInput 循环输入选择, 或者直接回车退出
|
||||
func LoopInput(tip string, choices interface{}, singleRowPrint bool) int {
|
||||
reflectValue := reflect.ValueOf(choices)
|
||||
if reflectValue.Kind() != reflect.Slice && reflectValue.Kind() != reflect.Array {
|
||||
fmt.Println("only support slice or array type!")
|
||||
return -1
|
||||
}
|
||||
length := reflectValue.Len()
|
||||
if reflectValue.Type().String() == "[]string" {
|
||||
if singleRowPrint {
|
||||
for i := 0; i < length; i++ {
|
||||
fmt.Printf("%d.%s\n\n", i+1, reflectValue.Index(i).Interface())
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < length; i++ {
|
||||
if i%2 == 0 {
|
||||
fmt.Printf("%d.%-15s\t", i+1, reflectValue.Index(i).Interface())
|
||||
} else {
|
||||
fmt.Printf("%d.%-15s\n\n", i+1, reflectValue.Index(i).Interface())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
inputString := ""
|
||||
if length < 10 {
|
||||
inputString = getChar(tip)
|
||||
} else {
|
||||
fmt.Print(tip)
|
||||
_, _ = fmt.Scanln(&inputString)
|
||||
}
|
||||
if inputString == "" {
|
||||
return -1
|
||||
} else if !IsInteger(inputString) {
|
||||
fmt.Println("输入有误,请重新输入")
|
||||
continue
|
||||
}
|
||||
number, _ := strconv.Atoi(inputString)
|
||||
if number <= length && number > 0 {
|
||||
return number
|
||||
}
|
||||
fmt.Println("输入数字越界,请重新输入")
|
||||
}
|
||||
}
|
||||
|
||||
// Input 读取终端用户输入
|
||||
func Input(tip string, defaultValue string) string {
|
||||
input := ""
|
||||
fmt.Print(tip)
|
||||
_, _ = fmt.Scanln(&input)
|
||||
if input == "" && defaultValue != "" {
|
||||
input = defaultValue
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
// Red 红色
|
||||
func Red(str string) string {
|
||||
return RED + str + RESET
|
||||
}
|
||||
|
||||
// Green 绿色
|
||||
func Green(str string) string {
|
||||
return GREEN + str + RESET
|
||||
}
|
||||
|
||||
// Yellow 黄色
|
||||
func Yellow(str string) string {
|
||||
return YELLOW + str + RESET
|
||||
}
|
||||
|
||||
// Blue 蓝色
|
||||
func Blue(str string) string {
|
||||
return BLUE + str + RESET
|
||||
}
|
||||
|
||||
// Fuchsia 紫红色
|
||||
func Fuchsia(str string) string {
|
||||
return FUCHSIA + str + RESET
|
||||
}
|
||||
|
||||
// Cyan 青色
|
||||
func Cyan(str string) string {
|
||||
return CYAN + str + RESET
|
||||
}
|
||||
|
||||
// White 白色
|
||||
func White(str string) string {
|
||||
return WHITE + str + RESET
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// http升级websocket协议的配置
|
||||
var wsUpgrader = websocket.Upgrader{
|
||||
// 允许所有CORS跨域请求
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// WsMessage websocket消息
|
||||
type WsMessage struct {
|
||||
MessageType int
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// WsConnection 封装websocket连接
|
||||
type WsConnection struct {
|
||||
wsSocket *websocket.Conn // 底层websocket
|
||||
inChan chan *WsMessage // 读取队列
|
||||
outChan chan *WsMessage // 发送队列
|
||||
|
||||
mutex sync.Mutex // 避免重复关闭管道
|
||||
isClosed bool
|
||||
CloseChan chan byte // 关闭通知
|
||||
}
|
||||
|
||||
// 读取协程
|
||||
func (wsConn *WsConnection) wsReadLoop() {
|
||||
var (
|
||||
msgType int
|
||||
data []byte
|
||||
msg *WsMessage
|
||||
err error
|
||||
)
|
||||
for {
|
||||
// 读一个message
|
||||
if msgType, data, err = wsConn.wsSocket.ReadMessage(); err != nil {
|
||||
fmt.Println("Read error: " + err.Error())
|
||||
goto CLOSED
|
||||
}
|
||||
msg = &WsMessage{
|
||||
msgType,
|
||||
data,
|
||||
}
|
||||
// 放入请求队列
|
||||
select {
|
||||
case wsConn.inChan <- msg:
|
||||
if string(data) == "exit" {
|
||||
goto CLOSED
|
||||
}
|
||||
case <-wsConn.CloseChan:
|
||||
goto CLOSED
|
||||
}
|
||||
}
|
||||
CLOSED:
|
||||
wsConn.WsClose()
|
||||
}
|
||||
|
||||
// 发送协程
|
||||
func (wsConn *WsConnection) wsWriteLoop() {
|
||||
var (
|
||||
msg *WsMessage
|
||||
err error
|
||||
)
|
||||
for {
|
||||
select {
|
||||
// 取一个应答
|
||||
case msg = <-wsConn.outChan:
|
||||
// 写给websocket
|
||||
if err = wsConn.wsSocket.WriteMessage(msg.MessageType, msg.Data); err != nil {
|
||||
fmt.Println(err)
|
||||
goto CLOSED
|
||||
}
|
||||
case <-wsConn.CloseChan:
|
||||
goto CLOSED
|
||||
}
|
||||
}
|
||||
CLOSED:
|
||||
wsConn.WsClose()
|
||||
}
|
||||
|
||||
// InitWebsocket 初始化ws
|
||||
func InitWebsocket(resp http.ResponseWriter, req *http.Request) (wsConn *WsConnection, err error) {
|
||||
var (
|
||||
wsSocket *websocket.Conn
|
||||
)
|
||||
// 应答客户端告知升级连接为websocket
|
||||
if wsSocket, err = wsUpgrader.Upgrade(resp, req, nil); err != nil {
|
||||
return
|
||||
}
|
||||
wsConn = &WsConnection{
|
||||
wsSocket: wsSocket,
|
||||
inChan: make(chan *WsMessage, 1000),
|
||||
outChan: make(chan *WsMessage, 1000),
|
||||
CloseChan: make(chan byte),
|
||||
isClosed: false,
|
||||
}
|
||||
|
||||
// 读协程
|
||||
go wsConn.wsReadLoop()
|
||||
// 写协程
|
||||
go wsConn.wsWriteLoop()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// WsWrite 发送消息
|
||||
func (wsConn *WsConnection) WsWrite(messageType int, data []byte) (err error) {
|
||||
select {
|
||||
case wsConn.outChan <- &WsMessage{messageType, data}:
|
||||
case <-wsConn.CloseChan:
|
||||
err = errors.New("websocket closed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WsRead 读取消息
|
||||
func (wsConn *WsConnection) WsRead() (msg *WsMessage, err error) {
|
||||
select {
|
||||
case msg = <-wsConn.inChan:
|
||||
return
|
||||
case <-wsConn.CloseChan:
|
||||
err = errors.New("websocket closed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WsClose 关闭连接
|
||||
func (wsConn *WsConnection) WsClose() {
|
||||
wsConn.wsSocket.Close()
|
||||
wsConn.mutex.Lock()
|
||||
defer wsConn.mutex.Unlock()
|
||||
if !wsConn.isClosed {
|
||||
wsConn.isClosed = true
|
||||
close(wsConn.CloseChan)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user