wip: 0.2.0
1. websocket 连接,退出,消息 2. 基本页面
This commit is contained in:
@ -23,6 +23,11 @@ func Start(ctx context.Context) <-chan struct{} {
|
||||
app.Post("/api/ushare/:code", handler.ShareUpload()) // 分片上传接口
|
||||
app.Post("/api/uauth/login", handler.AuthLogin())
|
||||
|
||||
{
|
||||
api := app.Group("/api/ulocal")
|
||||
api.Get("/registry", handler.LocalRegistry())
|
||||
}
|
||||
|
||||
ready := make(chan struct{})
|
||||
ln, err := net.Listen("tcp", opt.Cfg.Address)
|
||||
if err != nil {
|
||||
|
172
internal/controller/room.go
Normal file
172
internal/controller/room.go
Normal file
@ -0,0 +1,172 @@
|
||||
// room controller:
|
||||
// local share websocket room controller
|
||||
// same remote ip as a
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
"github.com/loveuer/ushare/internal/pkg/tool"
|
||||
"github.com/mileusna/useragent"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RoomClientType string
|
||||
|
||||
const (
|
||||
ClientTypeDesktop RoomClientType = "desktop"
|
||||
ClientTypeMobile RoomClientType = "mobile"
|
||||
ClientTypeTablet RoomClientType = "tablet"
|
||||
)
|
||||
|
||||
type RoomAppType string
|
||||
|
||||
const (
|
||||
RoomAppTypeWeb = "web"
|
||||
)
|
||||
|
||||
type roomClient struct {
|
||||
controller *roomController
|
||||
conn *websocket.Conn
|
||||
clientType RoomClientType
|
||||
appType RoomAppType
|
||||
ip string
|
||||
name string
|
||||
id string
|
||||
msgChan chan any
|
||||
}
|
||||
|
||||
func (rc *roomClient) start(ctx context.Context) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case msg, _ := <-rc.msgChan:
|
||||
err := rc.conn.WriteJSON(msg)
|
||||
log.Debug("RoomClient: write json message, ip = %s, id = %s, name = %s, err = %v", rc.ip, rc.id, rc.name, err)
|
||||
if err != nil {
|
||||
log.Error("RoomClient: write json message failed, ip = %s, id = %s, name = %s, err = %s", rc.ip, rc.id, rc.name, err.Error())
|
||||
}
|
||||
default:
|
||||
mt, bs, err := rc.conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Error("RoomClient: read message failed, ip = %s, id = %s, name = %s, err = %s", rc.ip, rc.id, rc.name, err.Error())
|
||||
rc.controller.Unregister(rc)
|
||||
return
|
||||
}
|
||||
|
||||
switch mt {
|
||||
case websocket.PingMessage:
|
||||
rs, _ := json.Marshal(map[string]any{"type": "pong", "time": time.Now().UnixMilli(), "id": rc.id, "name": rc.name})
|
||||
if err := rc.conn.WriteMessage(websocket.PongMessage, rs); err != nil {
|
||||
log.Error("RoomClient: response ping message failed, ip = %s, id = %s, name = %s, err = %s", rc.ip, rc.id, rc.name, err.Error())
|
||||
}
|
||||
case websocket.CloseMessage:
|
||||
log.Debug("RoomClient: received close message, unregister ip = %s id = %s, name = %s", rc.ip, rc.id, rc.name)
|
||||
rc.controller.Unregister(rc)
|
||||
return
|
||||
case websocket.TextMessage:
|
||||
log.Info("RoomClient: received text message, ip = %s, id = %s, name = %s, text = %s", rc.ip, rc.id, rc.name, string(bs))
|
||||
case websocket.BinaryMessage:
|
||||
// todo
|
||||
log.Info("RoomClient: received bytes message, ip = %s, id = %s, name = %s, text = %s", rc.ip, rc.id, rc.name, string(bs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
type roomController struct {
|
||||
sync.Mutex
|
||||
ctx context.Context
|
||||
rooms map[string]map[string]*roomClient // map[room_id(remote-ip)][id]
|
||||
}
|
||||
|
||||
var (
|
||||
RoomController = &roomController{
|
||||
rooms: make(map[string]map[string]*roomClient),
|
||||
}
|
||||
)
|
||||
|
||||
func (rc *roomController) Start(ctx context.Context) {
|
||||
rc.ctx = ctx
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case now := <-ticker.C:
|
||||
for room := range rc.rooms {
|
||||
rc.Broadcast(room, now.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (rc *roomController) Register(c *websocket.Conn, ip, userAgent string) {
|
||||
nrc := &roomClient{
|
||||
controller: rc,
|
||||
conn: c,
|
||||
clientType: ClientTypeDesktop,
|
||||
appType: RoomAppTypeWeb,
|
||||
ip: ip,
|
||||
id: uuid.Must(uuid.NewV7()).String(),
|
||||
name: tool.RandomName(),
|
||||
msgChan: make(chan any, 1),
|
||||
}
|
||||
|
||||
ua := useragent.Parse(userAgent)
|
||||
switch {
|
||||
case ua.Mobile:
|
||||
nrc.clientType = ClientTypeMobile
|
||||
case ua.Tablet:
|
||||
nrc.clientType = ClientTypeTablet
|
||||
}
|
||||
|
||||
key := "local"
|
||||
if !tool.IsPrivateIP(ip) {
|
||||
key = ip
|
||||
}
|
||||
|
||||
rc.Lock()
|
||||
|
||||
if _, ok := rc.rooms[key]; !ok {
|
||||
rc.rooms[key] = make(map[string]*roomClient)
|
||||
}
|
||||
|
||||
nrc.start(rc.ctx)
|
||||
log.Debug("controller.room: registry client, ip = %s(%s), id = %s, name = %s", key, nrc.ip, nrc.id, nrc.name)
|
||||
rc.rooms[key][nrc.id] = nrc
|
||||
|
||||
rc.Unlock()
|
||||
|
||||
rc.Broadcast(key, "new member")
|
||||
}
|
||||
|
||||
func (rc *roomController) Broadcast(room string, msg any) {
|
||||
for _, client := range rc.rooms[room] {
|
||||
client.msgChan <- msg
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *roomController) Unregister(client *roomClient) {
|
||||
key := "local"
|
||||
if !tool.IsPrivateIP(client.ip) {
|
||||
key = client.ip
|
||||
}
|
||||
|
||||
rc.Lock()
|
||||
defer rc.Unlock()
|
||||
|
||||
log.Debug("controller.room: unregister client, ip = %s(%s), id = %s, name = %s", client.ip, key, client.id, client.name)
|
||||
|
||||
delete(rc.rooms[key], client.id)
|
||||
}
|
35
internal/handler/local.go
Normal file
35
internal/handler/local.go
Normal file
@ -0,0 +1,35 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/loveuer/nf"
|
||||
"github.com/loveuer/nf/nft/log"
|
||||
"github.com/loveuer/ushare/internal/controller"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func LocalRegistry() nf.HandlerFunc {
|
||||
upgrader := websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
return func(c *nf.Ctx) error {
|
||||
|
||||
ip := c.IP(true)
|
||||
ua := c.Get("User-Agent")
|
||||
|
||||
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
log.Error("LocalRegistry: failed to upgrade websocket connection, err = %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
controller.RoomController.Register(conn, ip, ua)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
59
internal/pkg/tool/ip.go
Normal file
59
internal/pkg/tool/ip.go
Normal file
@ -0,0 +1,59 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
privateIPv4Blocks []*net.IPNet
|
||||
privateIPv6Blocks []*net.IPNet
|
||||
)
|
||||
|
||||
func init() {
|
||||
// IPv4私有地址段
|
||||
for _, cidr := range []string{
|
||||
"10.0.0.0/8", // A类私有地址
|
||||
"172.16.0.0/12", // B类私有地址
|
||||
"192.168.0.0/16", // C类私有地址
|
||||
"169.254.0.0/16", // 链路本地地址
|
||||
"127.0.0.0/8", // 环回地址
|
||||
} {
|
||||
_, block, _ := net.ParseCIDR(cidr)
|
||||
privateIPv4Blocks = append(privateIPv4Blocks, block)
|
||||
}
|
||||
|
||||
// IPv6私有地址段
|
||||
for _, cidr := range []string{
|
||||
"fc00::/7", // 唯一本地地址
|
||||
"fe80::/10", // 链路本地地址
|
||||
"::1/128", // 环回地址
|
||||
} {
|
||||
_, block, _ := net.ParseCIDR(cidr)
|
||||
privateIPv6Blocks = append(privateIPv6Blocks, block)
|
||||
}
|
||||
}
|
||||
|
||||
func IsPrivateIP(ipStr string) bool {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 处理IPv4和IPv4映射的IPv6地址
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
for _, block := range privateIPv4Blocks {
|
||||
if block.Contains(ip4) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 处理IPv6地址
|
||||
for _, block := range privateIPv6Blocks {
|
||||
if block.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -3,14 +3,31 @@ package tool
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
)
|
||||
|
||||
var (
|
||||
letters = []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
letterNum = []byte("0123456789")
|
||||
letterLow = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||
letterCap = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
letterSyb = []byte("!@#$%^&*()_+-=")
|
||||
letters = []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
letterNum = []byte("0123456789")
|
||||
letterLow = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||
letterCap = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
letterSyb = []byte("!@#$%^&*()_+-=")
|
||||
adjectives = []string{
|
||||
"开心的", "灿烂的", "温暖的", "阳光的", "活泼的",
|
||||
"聪明的", "优雅的", "幸运的", "甜蜜的", "勇敢的",
|
||||
"宁静的", "热情的", "温柔的", "幽默的", "坚强的",
|
||||
"迷人的", "神奇的", "快乐的", "健康的", "自由的",
|
||||
"梦幻的", "勤劳的", "真诚的", "浪漫的", "自信的",
|
||||
}
|
||||
|
||||
plants = []string{
|
||||
"苹果", "香蕉", "橘子", "葡萄", "草莓",
|
||||
"西瓜", "樱桃", "菠萝", "柠檬", "蜜桃",
|
||||
"蓝莓", "芒果", "石榴", "甜瓜", "雪梨",
|
||||
"番茄", "南瓜", "土豆", "青椒", "洋葱",
|
||||
"黄瓜", "萝卜", "豌豆", "玉米", "蘑菇",
|
||||
"菠菜", "茄子", "芹菜", "莲藕", "西兰花",
|
||||
}
|
||||
)
|
||||
|
||||
func RandomInt(max int64) int64 {
|
||||
@ -52,3 +69,7 @@ func RandomPassword(length int, withSymbol bool) string {
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func RandomName() string {
|
||||
return adjectives[mrand.Intn(len(adjectives))] + plants[mrand.Intn(len(plants))]
|
||||
}
|
||||
|
Reference in New Issue
Block a user