mirror of
https://github.com/m5stack/StackChan.git
synced 2026-04-27 11:02:40 +00:00
server code
This commit is contained in:
+2
-1
@@ -1 +1,2 @@
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
.idea/
|
||||
@@ -0,0 +1,7 @@
|
||||
ROOT_DIR = $(shell pwd)
|
||||
NAMESPACE = "default"
|
||||
DEPLOY_NAME = "template-single"
|
||||
DOCKER_NAME = "template-single"
|
||||
|
||||
include ./hack/hack-cli.mk
|
||||
include ./hack/hack.mk
|
||||
+44
-1
@@ -1 +1,44 @@
|
||||
# StackChan Server
|
||||
# StackChan Server
|
||||
|
||||
**StackChan Server** is the Server of the open-source StackChan project. It handles core functionalities such
|
||||
as device interactions, post management, and comment systems, providing stable and efficient API support.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- App and StackChan communication and interaction
|
||||
- Device post creation and management (text and image support, similar to a social feed)
|
||||
- Comment CRUD (Create, Read, Update, Delete) operations
|
||||
- Dance control and data management
|
||||
- Persistent storage using a relational database
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Go**: The project is developed in Go. Install **Go 1.24+** from
|
||||
the [official download page](https://golang.google.cn/dl/).
|
||||
|
||||
Verify installation:
|
||||
|
||||
```bash
|
||||
go version
|
||||
# Expected output: "go version go1.24.x ..." (or similar)
|
||||
|
||||
### Clone the Repository
|
||||
```bash
|
||||
git clone https://github.com/m5stack/StackChan # Replace with the actual repository URL
|
||||
cd StackChan/server
|
||||
|
||||
# Download dependencies
|
||||
go mod download
|
||||
|
||||
# build
|
||||
go build -o StackChan main.go
|
||||
|
||||
# Start running
|
||||
StackChan # Linux/macOS
|
||||
StackChan.exe # Windows
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package dance
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"stackChan/api/dance/v1"
|
||||
)
|
||||
|
||||
type IDanceV1 interface {
|
||||
Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error)
|
||||
Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error)
|
||||
Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error)
|
||||
GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"stackChan/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type CreateReq struct {
|
||||
g.Meta `path:"/dance" method:"post" tags:"Dance" summary:"Dance create request"`
|
||||
Mac string `json:"mac" v:"required"`
|
||||
Index int `json:"index" v:"required"`
|
||||
List []model.DanceData `json:"list" v:"required"`
|
||||
}
|
||||
|
||||
type CreateRes string
|
||||
|
||||
type DeleteReq struct {
|
||||
g.Meta `path:"/dance" method:"delete" tags:"Dance" summary:"Dance delete request"`
|
||||
Mac string `json:"mac" v:"required"`
|
||||
Index int `json:"index" v:"required"`
|
||||
}
|
||||
|
||||
type DeleteRes string
|
||||
|
||||
type UpdateReq struct {
|
||||
g.Meta `path:"/dance" method:"put" tags:"Dance" summary:"Dance put request"`
|
||||
Mac string `json:"mac" v:"required"`
|
||||
Index int `json:"index" v:"required"`
|
||||
Data []model.DanceData `json:"list" v:"required"`
|
||||
}
|
||||
|
||||
type UpdateRes string
|
||||
|
||||
type GetListReq struct {
|
||||
g.Meta `path:"/dance" method:"get" tags:"Dance" summary:"Dance get request"`
|
||||
Mac string `json:"mac" v:"required"`
|
||||
}
|
||||
|
||||
type GetListRes map[string][]model.DanceData
|
||||
@@ -0,0 +1,19 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"stackChan/api/device/v1"
|
||||
)
|
||||
|
||||
type IDeviceV1 interface {
|
||||
Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error)
|
||||
Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error)
|
||||
GetRandomDevice(ctx context.Context, req *v1.GetRandomDeviceReq) (res *v1.GetRandomDeviceRes, err error)
|
||||
GetDeviceInfo(ctx context.Context, req *v1.GetDeviceInfoReq) (res *v1.GetDeviceInfoRes, err error)
|
||||
UpdateDeviceInfo(ctx context.Context, req *v1.UpdateDeviceInfoReq) (res *v1.UpdateDeviceInfoRes, err error)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type CreateReq struct {
|
||||
g.Meta `path:"/device" method:"post" tags:"Device" summary:"Device create request"`
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
Name string `json:"name,omitempty" description:"Device name"`
|
||||
}
|
||||
|
||||
type CreateRes struct {
|
||||
Id int64 `json:"id" dc:"Device id"`
|
||||
}
|
||||
|
||||
type UpdateReq struct {
|
||||
g.Meta `path:"/device" method:"put" tags:"Device" summary:"Device update request"`
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
Name string `json:"name" description:"Device name"`
|
||||
}
|
||||
|
||||
type UpdateRes struct{}
|
||||
|
||||
type GetRandomDeviceReq struct {
|
||||
g.Meta `path:"/device/randomList" method:"get" tags:"Device" summary:"Device get Random"`
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
}
|
||||
|
||||
type GetRandomDeviceRes []entity.Device
|
||||
|
||||
type GetDeviceInfoReq struct {
|
||||
g.Meta `path:"/device/info" method:"get" tags:"Device" summary:"Device Info Get request"`
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
}
|
||||
|
||||
type GetDeviceInfoRes model.DeviceInfo
|
||||
|
||||
type UpdateDeviceInfoReq struct {
|
||||
g.Meta `path:"/device/info" method:"put" tags:"Device" summary:"Device Info Put request"`
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
Name string `json:"name" description:"Device name"`
|
||||
}
|
||||
|
||||
type UpdateDeviceInfoRes string
|
||||
@@ -0,0 +1,15 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package file
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"stackChan/api/file/v1"
|
||||
)
|
||||
|
||||
type IFileV1 interface {
|
||||
File(ctx context.Context, req *v1.FileReq) (res *v1.FileRes, err error)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
type FileReq struct {
|
||||
g.Meta `path:"/uploadFile" method:"post" tags:"File" summary:"File upload request"`
|
||||
File *ghttp.UploadFile `json:"file" v:"required" description:"File upload request"`
|
||||
Name string `json:"name" v:"required" description:"File Name"`
|
||||
Directory string `json:"directory" description:"Directory upload request"`
|
||||
}
|
||||
|
||||
type FileRes struct {
|
||||
Path string `json:"path" description:"file path"`
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package friend
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"stackChan/api/friend/v1"
|
||||
)
|
||||
|
||||
type IFriendV1 interface {
|
||||
Add(ctx context.Context, req *v1.AddReq) (res *v1.AddRes, err error)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
type AddReq struct {
|
||||
g.Meta `path:"/friend" method:"post" tags:"Friend" summary:"Friend add request"`
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
FriendMac string `json:"friendMac" v:"required" description:"Friend Mac address"`
|
||||
}
|
||||
|
||||
type AddRes string
|
||||
@@ -0,0 +1,20 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package post
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"stackChan/api/post/v1"
|
||||
)
|
||||
|
||||
type IPostV1 interface {
|
||||
CreatePost(ctx context.Context, req *v1.CreatePostReq) (res *v1.CreatePostRes, err error)
|
||||
GetPost(ctx context.Context, req *v1.GetPostReq) (res *v1.GetPostRes, err error)
|
||||
DeletePost(ctx context.Context, req *v1.DeletePostReq) (res *v1.DeletePostRes, err error)
|
||||
CreatePostComment(ctx context.Context, req *v1.CreatePostCommentReq) (res *v1.CreatePostCommentRes, err error)
|
||||
DeletePostComment(ctx context.Context, req *v1.DeletePostCommentReq) (res *v1.DeletePostCommentRes, err error)
|
||||
GetPostComment(ctx context.Context, req *v1.GetPostCommentReq) (res *v1.GetPostCommentRes, err error)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"stackChan/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type CreatePostReq struct {
|
||||
g.Meta `path:"/post/add" method:"post" tags:"Post" summary:"Post create request"`
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
ContentText string `json:"content_text" v:"required" description:"Content text"`
|
||||
ContentImage string `json:"content_image" v:"required" description:"Content image"`
|
||||
}
|
||||
|
||||
type CreatePostRes struct {
|
||||
Id int64 `json:"id"`
|
||||
}
|
||||
|
||||
type GetPostReq struct {
|
||||
g.Meta `path:"/post/get" method:"get" tags:"Post" summary:"Post get request"`
|
||||
Page int `json:"page" v:"required#Page不能为空" description:"页码"`
|
||||
PageSize int `json:"pageSize" v:"required#每页数量不能为空" description:"每页条数"`
|
||||
}
|
||||
|
||||
type GetPostRes []model.Post
|
||||
|
||||
type DeletePostReq struct {
|
||||
g.Meta `path:"/post/delete" method:"delete" tags:"Post" summary:"Post delete request"`
|
||||
Id int `json:"id" summary:"Post id"`
|
||||
}
|
||||
|
||||
type DeletePostRes string
|
||||
|
||||
type CreatePostCommentReq struct {
|
||||
g.Meta `path:"/post/comment/create" method:"post" tags:"Post" summary:"Post create comment"`
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
PostId int64 `json:"postId" v:"required" summary:"Post comment id"`
|
||||
Content string `json:"content" description:"评论内容"`
|
||||
}
|
||||
|
||||
type CreatePostCommentRes struct {
|
||||
Id int64 `json:"id"`
|
||||
}
|
||||
|
||||
type DeletePostCommentReq struct {
|
||||
g.Meta `path:"/post/comment/delete" method:"post" tags:"Post" summary:"Post delete comment"`
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
Id int `json:"id" summary:"Post comment id"`
|
||||
}
|
||||
|
||||
type DeletePostCommentRes struct{}
|
||||
|
||||
type GetPostCommentReq struct {
|
||||
g.Meta `path:"/post/comment/get" method:"get" tags:"Post" summary:"Post get comment"`
|
||||
PostId int64 `json:"postId" summary:"Post comment id"`
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
Page int `json:"page" summary:"Post comment page"`
|
||||
PageSize int `json:"pageSize" summary:"Post comment page"`
|
||||
}
|
||||
|
||||
type GetPostCommentRes struct {
|
||||
List []*model.PostComment `json:"list"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
module stackChan
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.7
|
||||
github.com/gogf/gf/v2 v2.9.7
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v1.1.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
)
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.7 h1:mzs0MblNT0pOlUB00c/hTcAenQ5N/cB651wh9VCJitc=
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.7/go.mod h1:Z2MgGyag0fZQ2+9ykafD7tQhau9h5ie3Chg/GUzfy5E=
|
||||
github.com/gogf/gf/v2 v2.9.7 h1:Vp3VGZ7drPs89tZslT6j6BKBTaw7Xs3DMGWx4MlVtMA=
|
||||
github.com/gogf/gf/v2 v2.9.7/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
||||
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
@@ -0,0 +1,12 @@
|
||||
#SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
#SPDX-License-Identifier: MIT
|
||||
gfcli:
|
||||
gen:
|
||||
dao:
|
||||
- link: "sqlite::@file(/stackChan.sqlite)"
|
||||
descriptionTag: true
|
||||
|
||||
docker:
|
||||
build: "-a amd64 -s linux -p temp -ew"
|
||||
tagPrefixes:
|
||||
- my.image.pub/my-app
|
||||
@@ -0,0 +1,21 @@
|
||||
#SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
#SPDX-License-Identifier: MIT
|
||||
# Install/Update to the latest CLI tool.
|
||||
.PHONY: cli
|
||||
cli:
|
||||
@set -e; \
|
||||
wget -O gf \
|
||||
https://github.com/gogf/gf/releases/latest/download/gf_$(shell go env GOOS)_$(shell go env GOARCH) && \
|
||||
chmod +x gf && \
|
||||
./gf install -y && \
|
||||
rm ./gf
|
||||
|
||||
|
||||
# Check and install CLI tool.
|
||||
.PHONY: cli.install
|
||||
cli.install:
|
||||
@set -e; \
|
||||
gf -v > /dev/null 2>&1 || if [[ "$?" -ne "0" ]]; then \
|
||||
echo "GoFame CLI is not installed, start proceeding auto installation..."; \
|
||||
make cli; \
|
||||
fi;
|
||||
@@ -0,0 +1,78 @@
|
||||
#SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
#SPDX-License-Identifier: MIT
|
||||
|
||||
.DEFAULT_GOAL := build
|
||||
|
||||
# Update GoFrame and its CLI to latest stable version.
|
||||
.PHONY: up
|
||||
up: cli.install
|
||||
@gf up -a
|
||||
|
||||
# Build binary using configuration from hack/config.yaml.
|
||||
.PHONY: build
|
||||
build: cli.install
|
||||
@gf build -ew
|
||||
|
||||
# Parse api and generate controller/sdk.
|
||||
.PHONY: ctrl
|
||||
ctrl: cli.install
|
||||
@gf gen ctrl
|
||||
|
||||
# Generate Go files for DAO/DO/Entity.
|
||||
.PHONY: dao
|
||||
dao: cli.install
|
||||
@gf gen dao
|
||||
|
||||
# Parse current project go files and generate enums go file.
|
||||
.PHONY: enums
|
||||
enums: cli.install
|
||||
@gf gen enums
|
||||
|
||||
# Generate Go files for Service.
|
||||
.PHONY: service
|
||||
service: cli.install
|
||||
@gf gen service
|
||||
|
||||
|
||||
# Build docker image.
|
||||
.PHONY: image
|
||||
image: cli.install
|
||||
$(eval _TAG = $(shell git rev-parse --short HEAD))
|
||||
ifneq (, $(shell git status --porcelain 2>/dev/null))
|
||||
$(eval _TAG = $(_TAG).dirty)
|
||||
endif
|
||||
$(eval _TAG = $(if ${TAG}, ${TAG}, $(_TAG)))
|
||||
$(eval _PUSH = $(if ${PUSH}, ${PUSH}, ))
|
||||
@gf docker ${_PUSH} -tn $(DOCKER_NAME):${_TAG};
|
||||
|
||||
|
||||
# Build docker image and automatically push to docker repo.
|
||||
.PHONY: image.push
|
||||
image.push: cli.install
|
||||
@make image PUSH=-p;
|
||||
|
||||
|
||||
# Deploy image and yaml to current kubectl environment.
|
||||
.PHONY: deploy
|
||||
deploy: cli.install
|
||||
$(eval _TAG = $(if ${TAG}, ${TAG}, develop))
|
||||
|
||||
@set -e; \
|
||||
mkdir -p $(ROOT_DIR)/temp/kustomize;\
|
||||
cd $(ROOT_DIR)/manifest/deploy/kustomize/overlays/${_ENV};\
|
||||
kustomize build > $(ROOT_DIR)/temp/kustomize.yaml;\
|
||||
kubectl apply -f $(ROOT_DIR)/temp/kustomize.yaml; \
|
||||
if [ $(DEPLOY_NAME) != "" ]; then \
|
||||
kubectl patch -n $(NAMESPACE) deployment/$(DEPLOY_NAME) -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"$(shell date +%s)\"}}}}}"; \
|
||||
fi;
|
||||
|
||||
|
||||
# Parsing protobuf files and generating go files.
|
||||
.PHONY: pb
|
||||
pb: cli.install
|
||||
@gf gen pb
|
||||
|
||||
# Generate protobuf files for database tables.
|
||||
.PHONY: pbentity
|
||||
pbentity: cli.install
|
||||
@gf gen pbentity
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"stackChan/internal/controller/dance"
|
||||
"stackChan/internal/controller/device"
|
||||
"stackChan/internal/controller/file"
|
||||
"stackChan/internal/controller/friend"
|
||||
"stackChan/internal/controller/post"
|
||||
"stackChan/internal/web_socket"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gtimer"
|
||||
)
|
||||
|
||||
var (
|
||||
Main = gcmd.Command{
|
||||
Name: "main",
|
||||
Usage: "main",
|
||||
Brief: "start http server",
|
||||
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
|
||||
PrintIPAddr()
|
||||
|
||||
//Start a scheduled task to send ping messages
|
||||
gtimer.SetInterval(ctx, time.Second*5, func(ctx context.Context) {
|
||||
web_socket.StartPingTime(ctx)
|
||||
})
|
||||
//Start a timer to clean up long-lived connections that have been inactive for a long time on the app.
|
||||
gtimer.SetInterval(ctx, time.Second*15, func(ctx context.Context) {
|
||||
web_socket.CheckExpiredLinks(ctx)
|
||||
})
|
||||
|
||||
s := g.Server()
|
||||
s.BindHandler("/stackChan/ws", web_socket.Handler)
|
||||
|
||||
///Configuration file access
|
||||
s.Group("/file", func(group *ghttp.RouterGroup) {
|
||||
group.GET("/*filepath", func(r *ghttp.Request) {
|
||||
relativePath := r.Get("filepath").String()
|
||||
if relativePath == "" {
|
||||
r.Response.WriteHeader(http.StatusNotFound)
|
||||
r.Response.Write("File not found")
|
||||
return
|
||||
}
|
||||
filePath := filepath.Join("file", relativePath)
|
||||
if !gfile.Exists(filePath) {
|
||||
r.Response.WriteHeader(http.StatusNotFound)
|
||||
r.Response.Write("File not found")
|
||||
return
|
||||
}
|
||||
r.Response.ServeFile(filePath)
|
||||
})
|
||||
})
|
||||
|
||||
s.Group("/stackChan", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(ghttp.MiddlewareHandlerResponse)
|
||||
group.Bind(device.NewV1(), friend.NewV1(), dance.NewV1(), file.NewV1(), post.NewV1())
|
||||
})
|
||||
s.Run()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func PrintIPAddr() {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err == nil {
|
||||
fmt.Println("Local IP addresses detected on this machine:")
|
||||
for _, addr := range addrs {
|
||||
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil {
|
||||
fmt.Println(" -", ipnet.IP.String())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Could not detect local IP addresses:", err)
|
||||
}
|
||||
fmt.Println("Please update the StackChan and iOS client access addresses to use one of the above local IPs as needed.")
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package consts
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package dance
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package dance
|
||||
|
||||
import (
|
||||
"stackChan/api/dance"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() dance.IDanceV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package dance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"stackChan/api/dance/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
|
||||
if req.Index < 0 {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "Index cannot be negative")
|
||||
}
|
||||
|
||||
device, err := dao.Device.Ctx(ctx).Where("mac=", req.Mac).One()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if device.IsEmpty() {
|
||||
_, err = dao.Device.Ctx(ctx).Data(dao.Device.Columns().Mac, req.Mac).Insert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dance, err := dao.DeviceDance.Ctx(ctx).Where("mac=?", req.Mac).Where("dance_index=?", req.Index).One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
danceListJSON, err := json.Marshal(req.List)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dance.IsEmpty() {
|
||||
_, err = dao.DeviceDance.Ctx(ctx).Data(do.DeviceDance{
|
||||
Mac: req.Mac,
|
||||
DanceIndex: req.Index,
|
||||
DanceData: danceListJSON,
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
_, err = dao.DeviceDance.Ctx(ctx).Where("mac=?", req.Mac).Where("dance_index=?", req.Index).Data(do.DeviceDance{
|
||||
DanceData: danceListJSON,
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
response := v1.CreateRes("Dance data saved successfully")
|
||||
return &response, nil
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package dance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
|
||||
"stackChan/api/dance/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) {
|
||||
_, err = dao.DeviceDance.Ctx(ctx).Where("mac=", req.Mac).Where("dance_index=", req.Index).Delete()
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package dance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/do"
|
||||
"stackChan/internal/model/entity"
|
||||
"strconv"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"stackChan/api/dance/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) {
|
||||
danceMap := make(map[string][]model.DanceData)
|
||||
var list []entity.DeviceDance
|
||||
err = dao.DeviceDance.Ctx(ctx).Where(do.DeviceDance{
|
||||
Mac: req.Mac,
|
||||
}).Scan(&list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(list) > 0 {
|
||||
deviceDance := list[0]
|
||||
var danceList []model.DanceData
|
||||
err = json.Unmarshal([]byte(deviceDance.DanceData), &danceList)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeInvalidParameter, err)
|
||||
}
|
||||
key := strconv.Itoa(deviceDance.DanceIndex)
|
||||
danceMap[key] = danceList
|
||||
}
|
||||
response := v1.GetListRes(danceMap)
|
||||
return &response, nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package dance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"stackChan/api/dance/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) {
|
||||
response := v1.UpdateRes("")
|
||||
danceJSON, err := json.Marshal(req.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = dao.DeviceDance.Ctx(ctx).Where("mac=?", req.Mac).Where("dance_index=?", req.Index).Data(do.DeviceDance{
|
||||
DanceData: danceJSON,
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response = "Update successful"
|
||||
return &response, nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package device
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"stackChan/api/device"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() device.IDeviceV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/api/device/v1"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model/do"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
|
||||
insertId, err := dao.Device.Ctx(ctx).Data(do.Device{
|
||||
Mac: req.Mac,
|
||||
Name: req.Name,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &v1.CreateRes{
|
||||
Id: insertId,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
|
||||
"stackChan/api/device/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetDeviceInfo(ctx context.Context, req *v1.GetDeviceInfoReq) (res *v1.GetDeviceInfoRes, err error) {
|
||||
var info model.DeviceInfo
|
||||
err = dao.Device.Ctx(ctx).WherePri(req.Mac).Scan(&info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = (*v1.GetDeviceInfoRes)(&info)
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/api/device/v1"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model/entity"
|
||||
"stackChan/internal/web_socket"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetRandomDevice(ctx context.Context, req *v1.GetRandomDeviceReq) (res *v1.GetRandomDeviceRes, err error) {
|
||||
|
||||
// Obtain the list of online StackChan mac addresses (excluding the current user) from the websocket layer.
|
||||
macList := web_socket.GetRandomStackChanDevice(req.Mac, 6)
|
||||
|
||||
if len(macList) == 0 {
|
||||
res = (*v1.GetRandomDeviceRes)(&[]entity.Device{})
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Query device information based on the Mac list
|
||||
list := make([]entity.Device, 0, len(macList))
|
||||
err = dao.Device.
|
||||
Ctx(ctx).
|
||||
WhereIn("mac", macList).
|
||||
Scan(&list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = (*v1.GetRandomDeviceRes)(&list)
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"stackChan/api/device/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) {
|
||||
_, err = dao.Device.Ctx(ctx).Data(do.Device{
|
||||
Name: req.Name,
|
||||
}).WherePri(req.Mac).Update()
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"stackChan/api/device/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) UpdateDeviceInfo(ctx context.Context, req *v1.UpdateDeviceInfoReq) (res *v1.UpdateDeviceInfoRes, err error) {
|
||||
doDevice := do.Device{}
|
||||
if req.Name != "" {
|
||||
doDevice.Name = req.Name
|
||||
}
|
||||
_, err = dao.Device.Ctx(ctx).Data(doDevice).WherePri(req.Mac).Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := v1.UpdateDeviceInfoRes("Update successful")
|
||||
return &response, nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package file
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package file
|
||||
|
||||
import (
|
||||
"stackChan/api/file"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() file.IFileV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package file
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/service"
|
||||
|
||||
"stackChan/api/file/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) File(ctx context.Context, req *v1.FileReq) (res *v1.FileRes, err error) {
|
||||
return service.AddFile(ctx, req)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package friend
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package friend
|
||||
|
||||
import (
|
||||
"stackChan/api/friend"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() friend.IFriendV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package friend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"stackChan/api/friend/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Add(ctx context.Context, req *v1.AddReq) (res *v1.AddRes, err error) {
|
||||
if req.Mac == req.FriendMac {
|
||||
return nil, gerror.New("You cannot add yourself as a friend")
|
||||
}
|
||||
macA := req.Mac
|
||||
macB := req.FriendMac
|
||||
var friend entity.DeviceFriend
|
||||
err = dao.DeviceFriend.Ctx(ctx).
|
||||
Where("mac_a", macA).
|
||||
Where("mac_b", macB).
|
||||
Scan(&friend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if friend.MacA == "" {
|
||||
err = dao.DeviceFriend.Ctx(ctx).
|
||||
Where("mac_a", macB).
|
||||
Where("mac_b", macA).
|
||||
Scan(&friend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if friend.MacA != "" {
|
||||
res1 := v1.AddRes("Successfully added a friend")
|
||||
return &res1, nil
|
||||
}
|
||||
_, err = dao.DeviceFriend.Ctx(ctx).Data(entity.DeviceFriend{
|
||||
MacA: macA,
|
||||
MacB: macB,
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res2 := v1.AddRes("Successfully added a friend")
|
||||
return &res2, nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package post
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package post
|
||||
|
||||
import (
|
||||
"stackChan/api/post"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() post.IPostV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package post
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"stackChan/api/post/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) CreatePost(ctx context.Context, req *v1.CreatePostReq) (res *v1.CreatePostRes, err error) {
|
||||
device, err := dao.Device.Ctx(ctx).Where("mac", req.Mac).One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if device == nil {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidRequest, "The device does not exist or the Mac address is incorrect")
|
||||
}
|
||||
insertId, err := dao.DevicePost.Ctx(ctx).Data(do.DevicePost{
|
||||
Mac: req.Mac,
|
||||
ContentText: req.ContentText,
|
||||
ContentImage: req.ContentImage,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &v1.CreatePostRes{
|
||||
Id: insertId,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package post
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"stackChan/api/post/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) CreatePostComment(ctx context.Context, req *v1.CreatePostCommentReq) (res *v1.CreatePostCommentRes, err error) {
|
||||
id, err := dao.DevicePostComment.Ctx(ctx).Data(do.DevicePostComment{
|
||||
PostId: req.PostId,
|
||||
Mac: req.Mac,
|
||||
Content: req.Content,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &v1.CreatePostCommentRes{
|
||||
Id: id,
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package post
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/api/post/v1"
|
||||
"stackChan/internal/dao"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) DeletePost(ctx context.Context, req *v1.DeletePostReq) (res *v1.DeletePostRes, err error) {
|
||||
_, err = dao.DevicePost.Ctx(ctx).WherePri(req.Id).Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := v1.DeletePostRes("Deletion successful")
|
||||
return &response, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package post
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
|
||||
"stackChan/api/post/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) DeletePostComment(ctx context.Context, req *v1.DeletePostCommentReq) (res *v1.DeletePostCommentRes, err error) {
|
||||
var postComment model.PostComment
|
||||
err = dao.DevicePostComment.Ctx(ctx).Where("id=?", req.Id).Scan(&postComment)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if postComment.Id == 0 {
|
||||
return nil, errors.New("post not found")
|
||||
}
|
||||
|
||||
if postComment.Mac != req.Mac {
|
||||
return nil, errors.New("no authority to delete")
|
||||
}
|
||||
|
||||
_, err = dao.DevicePostComment.
|
||||
Ctx(ctx).
|
||||
Where("id = ? AND mac = ?", req.Id, req.Mac).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.DeletePostCommentRes{}, nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package post
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/entity"
|
||||
|
||||
"stackChan/api/post/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetPost(ctx context.Context, req *v1.GetPostReq) (res *v1.GetPostRes, err error) {
|
||||
page := req.Page
|
||||
pageSize := req.PageSize
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
db := dao.DevicePost.Ctx(ctx).As("dp").
|
||||
LeftJoin("device d", "dp.mac = d.mac")
|
||||
|
||||
_, err = db.Count("dp.id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var list []model.Post
|
||||
err = db.Fields(
|
||||
"dp.id",
|
||||
"dp.mac",
|
||||
"d.name",
|
||||
"dp.content_text",
|
||||
"dp.content_image",
|
||||
"dp.created_at",
|
||||
).Order("dp.created_at DESC").Limit((page-1)*pageSize, pageSize).Scan(&list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(list); i++ {
|
||||
var comments []*model.PostComment
|
||||
err = dao.DevicePostComment.Ctx(ctx).Where("post_id", list[i].Id).Order("created_at ASC").Scan(&comments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for j := 0; j < len(comments); j++ {
|
||||
mac := comments[j].Mac
|
||||
var device entity.Device
|
||||
err = dao.Device.Ctx(ctx).Where("mac", mac).Scan(&device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
comments[j].Name = device.Name
|
||||
}
|
||||
|
||||
list[i].PostCommentList = comments
|
||||
}
|
||||
res = (*v1.GetPostRes)(&list)
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package post
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/api/post/v1"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/entity"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetPostComment(ctx context.Context, req *v1.GetPostCommentReq) (res *v1.GetPostCommentRes, err error) {
|
||||
page := req.Page
|
||||
pageSize := req.PageSize
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 10
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var list []*model.PostComment
|
||||
|
||||
db := dao.DevicePostComment.Ctx(ctx).As("dp").Where("mac = ? AND post_id = ?", req.Mac, req.PostId)
|
||||
|
||||
total, err := db.Count()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Order("created_at ASC").Limit(offset, pageSize).Scan(&list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(list); i++ {
|
||||
mac := list[i].Mac
|
||||
var device entity.Device
|
||||
err = dao.Device.Ctx(ctx).Where("mac", mac).Scan(&device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list[i].Name = device.Name
|
||||
}
|
||||
|
||||
res = &v1.GetPostCommentRes{
|
||||
List: list,
|
||||
Total: total,
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"stackChan/internal/dao/internal"
|
||||
)
|
||||
|
||||
// deviceDao is the data access object for the table device.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type deviceDao struct {
|
||||
*internal.DeviceDao
|
||||
}
|
||||
|
||||
var (
|
||||
// Device is a globally accessible object for table device operations.
|
||||
Device = deviceDao{internal.NewDeviceDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"stackChan/internal/dao/internal"
|
||||
)
|
||||
|
||||
// deviceDanceDao is the data access object for the table device_dance.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type deviceDanceDao struct {
|
||||
*internal.DeviceDanceDao
|
||||
}
|
||||
|
||||
var (
|
||||
// DeviceDance is a globally accessible object for table device_dance operations.
|
||||
DeviceDance = deviceDanceDao{internal.NewDeviceDanceDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"stackChan/internal/dao/internal"
|
||||
)
|
||||
|
||||
// deviceFriendDao is the data access object for the table device_friend.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type deviceFriendDao struct {
|
||||
*internal.DeviceFriendDao
|
||||
}
|
||||
|
||||
var (
|
||||
// DeviceFriend is a globally accessible object for table device_friend operations.
|
||||
DeviceFriend = deviceFriendDao{internal.NewDeviceFriendDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"stackChan/internal/dao/internal"
|
||||
)
|
||||
|
||||
// devicePostDao is the data access object for the table device_post.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type devicePostDao struct {
|
||||
*internal.DevicePostDao
|
||||
}
|
||||
|
||||
var (
|
||||
// DevicePost is a globally accessible object for table device_post operations.
|
||||
DevicePost = devicePostDao{internal.NewDevicePostDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"stackChan/internal/dao/internal"
|
||||
)
|
||||
|
||||
// devicePostCommentDao is the data access object for the table device_post_comment.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type devicePostCommentDao struct {
|
||||
*internal.DevicePostCommentDao
|
||||
}
|
||||
|
||||
var (
|
||||
// DevicePostComment is a globally accessible object for table device_post_comment operations.
|
||||
DevicePostComment = devicePostCommentDao{internal.NewDevicePostCommentDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -0,0 +1,81 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// DeviceDao is the data access object for the table device.
|
||||
type DeviceDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns DeviceColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// DeviceColumns defines and stores column names for the table device.
|
||||
type DeviceColumns struct {
|
||||
Mac string //
|
||||
Name string //
|
||||
}
|
||||
|
||||
// deviceColumns holds the columns for the table device.
|
||||
var deviceColumns = DeviceColumns{
|
||||
Mac: "mac",
|
||||
Name: "name",
|
||||
}
|
||||
|
||||
// NewDeviceDao creates and returns a new DAO object for table data access.
|
||||
func NewDeviceDao(handlers ...gdb.ModelHandler) *DeviceDao {
|
||||
return &DeviceDao{
|
||||
group: "default",
|
||||
table: "device",
|
||||
columns: deviceColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *DeviceDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *DeviceDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *DeviceDao) Columns() DeviceColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *DeviceDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *DeviceDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model.Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *DeviceDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// DeviceDanceDao is the data access object for the table device_dance.
|
||||
type DeviceDanceDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns DeviceDanceColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// DeviceDanceColumns defines and stores column names for the table device_dance.
|
||||
type DeviceDanceColumns struct {
|
||||
Id string //
|
||||
Mac string // 设备MAC地址
|
||||
DanceIndex string // 舞蹈编号,初始为1~3,可扩展
|
||||
DanceData string // MotionData
|
||||
CreatedAt string //
|
||||
UpdatedAt string //
|
||||
}
|
||||
|
||||
// deviceDanceColumns holds the columns for the table device_dance.
|
||||
var deviceDanceColumns = DeviceDanceColumns{
|
||||
Id: "id",
|
||||
Mac: "mac",
|
||||
DanceIndex: "dance_index",
|
||||
DanceData: "dance_data",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewDeviceDanceDao creates and returns a new DAO object for table data access.
|
||||
func NewDeviceDanceDao(handlers ...gdb.ModelHandler) *DeviceDanceDao {
|
||||
return &DeviceDanceDao{
|
||||
group: "default",
|
||||
table: "device_dance",
|
||||
columns: deviceDanceColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *DeviceDanceDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *DeviceDanceDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *DeviceDanceDao) Columns() DeviceDanceColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *DeviceDanceDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *DeviceDanceDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model.Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *DeviceDanceDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// DeviceFriendDao is the data access object for the table device_friend.
|
||||
type DeviceFriendDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns DeviceFriendColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// DeviceFriendColumns defines and stores column names for the table device_friend.
|
||||
type DeviceFriendColumns struct {
|
||||
MacA string //
|
||||
MacB string //
|
||||
}
|
||||
|
||||
// deviceFriendColumns holds the columns for the table device_friend.
|
||||
var deviceFriendColumns = DeviceFriendColumns{
|
||||
MacA: "mac_a",
|
||||
MacB: "mac_b",
|
||||
}
|
||||
|
||||
// NewDeviceFriendDao creates and returns a new DAO object for table data access.
|
||||
func NewDeviceFriendDao(handlers ...gdb.ModelHandler) *DeviceFriendDao {
|
||||
return &DeviceFriendDao{
|
||||
group: "default",
|
||||
table: "device_friend",
|
||||
columns: deviceFriendColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *DeviceFriendDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *DeviceFriendDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *DeviceFriendDao) Columns() DeviceFriendColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *DeviceFriendDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *DeviceFriendDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model.Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *DeviceFriendDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// DevicePostDao is the data access object for the table device_post.
|
||||
type DevicePostDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns DevicePostColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// DevicePostColumns defines and stores column names for the table device_post.
|
||||
type DevicePostColumns struct {
|
||||
Id string //
|
||||
Mac string // 发帖设备MAC
|
||||
ContentText string // 文本内容
|
||||
ContentImage string // 图片URL
|
||||
CreatedAt string // 发帖时间
|
||||
}
|
||||
|
||||
// devicePostColumns holds the columns for the table device_post.
|
||||
var devicePostColumns = DevicePostColumns{
|
||||
Id: "id",
|
||||
Mac: "mac",
|
||||
ContentText: "content_text",
|
||||
ContentImage: "content_image",
|
||||
CreatedAt: "created_at",
|
||||
}
|
||||
|
||||
// NewDevicePostDao creates and returns a new DAO object for table data access.
|
||||
func NewDevicePostDao(handlers ...gdb.ModelHandler) *DevicePostDao {
|
||||
return &DevicePostDao{
|
||||
group: "default",
|
||||
table: "device_post",
|
||||
columns: devicePostColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *DevicePostDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *DevicePostDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *DevicePostDao) Columns() DevicePostColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *DevicePostDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *DevicePostDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model.Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *DevicePostDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// DevicePostCommentDao is the data access object for the table device_post_comment.
|
||||
type DevicePostCommentDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns DevicePostCommentColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// DevicePostCommentColumns defines and stores column names for the table device_post_comment.
|
||||
type DevicePostCommentColumns struct {
|
||||
Id string //
|
||||
PostId string // 帖子ID
|
||||
Mac string // 评论设备MAC
|
||||
Content string // 评论内容
|
||||
CreatedAt string // 评论时间
|
||||
}
|
||||
|
||||
// devicePostCommentColumns holds the columns for the table device_post_comment.
|
||||
var devicePostCommentColumns = DevicePostCommentColumns{
|
||||
Id: "id",
|
||||
PostId: "post_id",
|
||||
Mac: "mac",
|
||||
Content: "content",
|
||||
CreatedAt: "created_at",
|
||||
}
|
||||
|
||||
// NewDevicePostCommentDao creates and returns a new DAO object for table data access.
|
||||
func NewDevicePostCommentDao(handlers ...gdb.ModelHandler) *DevicePostCommentDao {
|
||||
return &DevicePostCommentDao{
|
||||
group: "default",
|
||||
table: "device_post_comment",
|
||||
columns: devicePostCommentColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *DevicePostCommentDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *DevicePostCommentDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *DevicePostCommentDao) Columns() DevicePostCommentColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *DevicePostCommentDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *DevicePostCommentDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model.Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *DevicePostCommentDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// SqliteSequenceDao is the data access object for the table sqlite_sequence.
|
||||
type SqliteSequenceDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns SqliteSequenceColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// SqliteSequenceColumns defines and stores column names for the table sqlite_sequence.
|
||||
type SqliteSequenceColumns struct {
|
||||
Name string //
|
||||
Seq string //
|
||||
}
|
||||
|
||||
// sqliteSequenceColumns holds the columns for the table sqlite_sequence.
|
||||
var sqliteSequenceColumns = SqliteSequenceColumns{
|
||||
Name: "name",
|
||||
Seq: "seq",
|
||||
}
|
||||
|
||||
// NewSqliteSequenceDao creates and returns a new DAO object for table data access.
|
||||
func NewSqliteSequenceDao(handlers ...gdb.ModelHandler) *SqliteSequenceDao {
|
||||
return &SqliteSequenceDao{
|
||||
group: "default",
|
||||
table: "sqlite_sequence",
|
||||
columns: sqliteSequenceColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *SqliteSequenceDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *SqliteSequenceDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *SqliteSequenceDao) Columns() SqliteSequenceColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *SqliteSequenceDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *SqliteSequenceDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model.Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *SqliteSequenceDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"stackChan/internal/dao/internal"
|
||||
)
|
||||
|
||||
// sqliteSequenceDao is the data access object for the table sqlite_sequence.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type sqliteSequenceDao struct {
|
||||
*internal.SqliteSequenceDao
|
||||
}
|
||||
|
||||
var (
|
||||
// SqliteSequence is a globally accessible object for table sqlite_sequence operations.
|
||||
SqliteSequence = sqliteSequenceDao{internal.NewSqliteSequenceDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
type DeviceInfo struct {
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
Name string `json:"name" v:"required" description:"Name"`
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// Device is the golang structure of table device for DAO operations like Where/Data.
|
||||
type Device struct {
|
||||
g.Meta `orm:"table:device, do:true"`
|
||||
Mac any //
|
||||
Name any //
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// DeviceDance is the golang structure of table device_dance for DAO operations like Where/Data.
|
||||
type DeviceDance struct {
|
||||
g.Meta `orm:"table:device_dance, do:true"`
|
||||
Id any //
|
||||
Mac any // 设备MAC地址
|
||||
DanceIndex any // 舞蹈编号,初始为1~3,可扩展
|
||||
DanceData any // MotionData
|
||||
CreatedAt *gtime.Time //
|
||||
UpdatedAt *gtime.Time //
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// DeviceFriend is the golang structure of table device_friend for DAO operations like Where/Data.
|
||||
type DeviceFriend struct {
|
||||
g.Meta `orm:"table:device_friend, do:true"`
|
||||
MacA any //
|
||||
MacB any //
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// DevicePost is the golang structure of table device_post for DAO operations like Where/Data.
|
||||
type DevicePost struct {
|
||||
g.Meta `orm:"table:device_post, do:true"`
|
||||
Id any //
|
||||
Mac any // 发帖设备MAC
|
||||
ContentText any // 文本内容
|
||||
ContentImage any // 图片URL
|
||||
CreatedAt *gtime.Time // 发帖时间
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// DevicePostComment is the golang structure of table device_post_comment for DAO operations like Where/Data.
|
||||
type DevicePostComment struct {
|
||||
g.Meta `orm:"table:device_post_comment, do:true"`
|
||||
Id any //
|
||||
PostId any // 帖子ID
|
||||
Mac any // 评论设备MAC
|
||||
Content any // 评论内容
|
||||
CreatedAt *gtime.Time // 评论时间
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// SqliteSequence is the golang structure of table sqlite_sequence for DAO operations like Where/Data.
|
||||
type SqliteSequence struct {
|
||||
g.Meta `orm:"table:sqlite_sequence, do:true"`
|
||||
Name any //
|
||||
Seq any //
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
// Device is the golang structure for table device.
|
||||
type Device struct {
|
||||
Mac string `json:"mac" orm:"mac" description:""` //
|
||||
Name string `json:"name" orm:"name" description:""` //
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// DeviceDance is the golang structure for table device_dance.
|
||||
type DeviceDance struct {
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
Mac string `json:"mac" orm:"mac" description:"设备MAC地址"` // 设备MAC地址
|
||||
DanceIndex int `json:"danceIndex" orm:"dance_index" description:"舞蹈编号,初始为1~3,可扩展"` // 舞蹈编号,初始为1~3,可扩展
|
||||
DanceData string `json:"danceData" orm:"dance_data" description:"MotionData"` // MotionData
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:""` //
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:""` //
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
// DeviceFriend is the golang structure for table device_friend.
|
||||
type DeviceFriend struct {
|
||||
MacA string `json:"macA" orm:"mac_a" description:""` //
|
||||
MacB string `json:"macB" orm:"mac_b" description:""` //
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// DevicePost is the golang structure for table device_post.
|
||||
type DevicePost struct {
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
Mac string `json:"mac" orm:"mac" description:"发帖设备MAC"` // 发帖设备MAC
|
||||
ContentText string `json:"contentText" orm:"content_text" description:"文本内容"` // 文本内容
|
||||
ContentImage string `json:"contentImage" orm:"content_image" description:"图片URL"` // 图片URL
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"发帖时间"` // 发帖时间
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// DevicePostComment is the golang structure for table device_post_comment.
|
||||
type DevicePostComment struct {
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
PostId int64 `json:"postId" orm:"post_id" description:"帖子ID"` // 帖子ID
|
||||
Mac string `json:"mac" orm:"mac" description:"评论设备MAC"` // 评论设备MAC
|
||||
Content string `json:"content" orm:"content" description:"评论内容"` // 评论内容
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"评论时间"` // 评论时间
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
// SqliteSequence is the golang structure for table sqlite_sequence.
|
||||
type SqliteSequence struct {
|
||||
Name string `json:"name" orm:"name" description:""` //
|
||||
Seq string `json:"seq" orm:"seq" description:""` //
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
type ExpressionData struct {
|
||||
Type string `json:"type"`
|
||||
LeftEye ExpressionItem `json:"leftEye"`
|
||||
RightEye ExpressionItem `json:"rightEye"`
|
||||
Mouth ExpressionItem `json:"mouth"`
|
||||
}
|
||||
|
||||
type ExpressionItem struct {
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
Rotation int `json:"rotation"`
|
||||
Weight int `json:"weight"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
type MotionData struct {
|
||||
Type string `json:"type"`
|
||||
PitchServo MotionDataItem `json:"pitchServo"`
|
||||
YawServo MotionDataItem `json:"yawServo"`
|
||||
}
|
||||
|
||||
type MotionDataItem struct {
|
||||
Angle int `json:"angle"`
|
||||
Speed int `json:"speed"`
|
||||
Rotate int `json:"rotate"`
|
||||
}
|
||||
|
||||
type DanceData struct {
|
||||
LeftEye ExpressionItem `json:"leftEye"`
|
||||
RightEye ExpressionItem `json:"rightEye"`
|
||||
Mouth ExpressionItem `json:"mouth"`
|
||||
PitchServo MotionDataItem `json:"pitchServo"`
|
||||
YawServo MotionDataItem `json:"yawServo"`
|
||||
DurationMs int `json:"durationMs"`
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import "github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
type Post struct {
|
||||
Id int64 `json:"id" orm:"id" description:"帖子ID"`
|
||||
Mac string `json:"mac" orm:"mac" description:"发帖设备MAC"`
|
||||
Name string `json:"name" orm:"name" description:"发帖设备名称"`
|
||||
ContentText string `json:"contentText" orm:"content_text" description:"文本内容"`
|
||||
ContentImage string `json:"contentImage" orm:"content_image" description:"图片URL"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"发帖时间"`
|
||||
PostCommentList []*PostComment `json:"postCommentList" orm:"postCommentList" description:"评论"`
|
||||
}
|
||||
|
||||
type PostComment struct {
|
||||
Id int `json:"id" orm:"id" description:""` //
|
||||
PostId int `json:"postId" orm:"post_id" description:""` //
|
||||
Mac string `json:"mac" orm:"mac" description:""` //
|
||||
Name string `json:"name" orm:"name" description:""` //
|
||||
Content string `json:"content" orm:"content" description:""` //
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:""` //
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package packed
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model/do"
|
||||
"stackChan/internal/model/entity"
|
||||
)
|
||||
|
||||
func CreateMacIfNotExists(ctx context.Context, mac string) (id int64, err error) {
|
||||
count, err := dao.Device.Ctx(ctx).Where("mac = ?", mac).Count()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if count > 0 {
|
||||
return 0, nil
|
||||
}
|
||||
id, err = dao.Device.Ctx(ctx).Data(do.Device{
|
||||
Mac: mac,
|
||||
}).InsertAndGetId()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func GetDeviceName(ctx context.Context, mac string) (name string, err error) {
|
||||
var device entity.Device
|
||||
err = dao.Device.Ctx(ctx).Where("mac = ?", mac).Fields("name").Scan(&device)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return device.Name, nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateMac(t *testing.T) {
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
v1 "stackChan/api/file/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
func AddFile(ctx context.Context, req *v1.FileReq) (res *v1.FileRes, err error) {
|
||||
currentDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseDir := "file"
|
||||
fileDir := filepath.Join(currentDir, baseDir)
|
||||
|
||||
if req.Directory != "" {
|
||||
fileDir = filepath.Join(fileDir, req.Directory)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(fileDir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(fileDir, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if req.File.Size == 0 || req.Name == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "file or filename is empty")
|
||||
}
|
||||
|
||||
filePath := filepath.Join(fileDir, req.Name)
|
||||
|
||||
file, err := req.File.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileBytes, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filePath, fileBytes, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = file.Close()
|
||||
|
||||
var returnPath string
|
||||
if req.Directory != "" {
|
||||
returnPath = filepath.Join(baseDir, req.Directory, req.Name)
|
||||
} else {
|
||||
returnPath = filepath.Join(baseDir, req.Name)
|
||||
}
|
||||
|
||||
return &v1.FileRes{
|
||||
Path: returnPath,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,830 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package web_socket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"stackChan/internal/service"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
Opus byte = 0x01
|
||||
Jpeg byte = 0x02
|
||||
ControlAvatar byte = 0x03
|
||||
ControlMotion byte = 0x04
|
||||
OnCamera byte = 0x05
|
||||
OffCamera byte = 0x06
|
||||
|
||||
TextMessage byte = 0x07
|
||||
RequestCall byte = 0x09
|
||||
RefuseCall byte = 0x0A
|
||||
AgreeCall byte = 0x0B
|
||||
HangupCall byte = 0x0C
|
||||
|
||||
UpdateDeviceName byte = 0x0D
|
||||
GetDeviceName byte = 0x0E
|
||||
|
||||
inCall byte = 0x0F
|
||||
|
||||
ping byte = 0x10
|
||||
pong byte = 0x11
|
||||
|
||||
OnPhoneScreen byte = 0x12
|
||||
OffPhoneScreen byte = 0x13
|
||||
Dance byte = 0x14
|
||||
GetAvatarPosture byte = 0x15
|
||||
|
||||
DeviceOffline byte = 0x16
|
||||
DeviceOnline byte = 0x17
|
||||
)
|
||||
|
||||
var (
|
||||
wsUpGrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
|
||||
logger.Errorf(r.Context(), "WebSocket Upgrade failed: %v", reason)
|
||||
},
|
||||
}
|
||||
logger = g.Log()
|
||||
stackChanClientPool = sync.Map{}
|
||||
appClientPool = sync.Map{}
|
||||
)
|
||||
|
||||
// AppClient indicates a WebSocket client connection on the App side
|
||||
type AppClient struct {
|
||||
Mac string
|
||||
Conn *websocket.Conn
|
||||
mu *sync.RWMutex
|
||||
DeviceId string
|
||||
LastTime time.Time
|
||||
}
|
||||
|
||||
// StackChanClient indicates a WebSocket client connection for the device end of a StackChan
|
||||
type StackChanClient struct {
|
||||
Mac string
|
||||
Conn *websocket.Conn
|
||||
mu *sync.RWMutex
|
||||
CameraSubscriptionList []*AppClient
|
||||
CallAppClient *AppClient
|
||||
phoneScreen bool
|
||||
LastTime time.Time
|
||||
}
|
||||
|
||||
func Handler(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
mac := r.Get("mac").String()
|
||||
deviceType := r.Get("deviceType").String()
|
||||
if mac == "" || deviceType == "" {
|
||||
r.Response.Write("The mac and deviceType parameters are empty.")
|
||||
return
|
||||
}
|
||||
|
||||
ws, err := wsUpGrader.Upgrade(r.Response.Writer, r.Request, nil)
|
||||
if err != nil {
|
||||
r.Response.Write(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if deviceType == "StackChan" {
|
||||
isHave := false
|
||||
var client *StackChanClient
|
||||
|
||||
stackChanClientPool.Range(func(key, value any) bool {
|
||||
macAddr := key.(string)
|
||||
stackChanClient := value.(*StackChanClient)
|
||||
|
||||
if macAddr == mac {
|
||||
isHave = true
|
||||
client = stackChanClient
|
||||
client.mu.Lock()
|
||||
client.Conn = ws
|
||||
if client.CallAppClient != nil {
|
||||
reconnectMsg := createStringMessage(TextMessage, "The equipment has been reconnected.")
|
||||
msgType := websocket.BinaryMessage
|
||||
forwardMessage(ctx, client.CallAppClient.Conn, &msgType, reconnectMsg, client.CallAppClient.mu)
|
||||
}
|
||||
if len(client.CameraSubscriptionList) > 0 {
|
||||
onMsg := createMessage(OnCamera, nil)
|
||||
onType := websocket.BinaryMessage
|
||||
forwardMessage(ctx, client.Conn, &onType, onMsg, client.mu)
|
||||
}
|
||||
client.LastTime = time.Now()
|
||||
client.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if !isHave {
|
||||
client = &StackChanClient{
|
||||
Mac: mac,
|
||||
Conn: ws,
|
||||
mu: &sync.RWMutex{},
|
||||
phoneScreen: false,
|
||||
LastTime: time.Now(),
|
||||
}
|
||||
addStackChenClient(ctx, client)
|
||||
} else {
|
||||
// notify app
|
||||
onlineMsg := createStringMessage(DeviceOnline, "Your StackChan has been launched.")
|
||||
msgType := websocket.BinaryMessage
|
||||
// Notify App
|
||||
appClients := getAppClients(client.Mac)
|
||||
for _, appClient := range appClients {
|
||||
forwardMessage(ctx, appClient.Conn, &msgType, onlineMsg, appClient.mu)
|
||||
}
|
||||
}
|
||||
logger.Info(ctx, "There is a StackChen connected to the service.", client.Mac)
|
||||
defer func() {
|
||||
logger.Info(ctx, "There is a StackChan that has disconnected.", mac, deviceType)
|
||||
}()
|
||||
for {
|
||||
messageType, msg, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
|
||||
logger.Infof(ctx, "StackChan Normal disconnection: mac=%s, deviceType=%s, Reason=%v", mac, deviceType, err)
|
||||
break
|
||||
}
|
||||
|
||||
var ne net.Error
|
||||
if errors.As(err, &ne) && ne.Temporary() {
|
||||
logger.Infof(ctx, "StackChan Temporary network error. Continue reading.: mac=%s,deviceType=%s,Error=%v", mac, deviceType, err)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Errorf(ctx, "StackChan Abnormal disconnection: mac=%s, deviceType=%s, Error=%v", mac, deviceType, err)
|
||||
break
|
||||
}
|
||||
//logger.Infof(ctx, "收到StackChan端消息%d", len(msg))
|
||||
readStackChanMessage(ctx, client, &messageType, &msg)
|
||||
}
|
||||
} else if deviceType == "App" {
|
||||
deviceId := r.Get("deviceId").String()
|
||||
if deviceId == "" {
|
||||
r.Response.Write("The deviceId parameter in the App end is empty.")
|
||||
return
|
||||
}
|
||||
var client *AppClient
|
||||
found := false
|
||||
clients := getAppClients(mac)
|
||||
for _, appClient := range clients {
|
||||
if appClient.DeviceId == deviceId && appClient.Mac == mac {
|
||||
// Already available. Update the connection.
|
||||
client = appClient
|
||||
client.mu.Lock()
|
||||
client.Conn = ws
|
||||
client.mu.Unlock()
|
||||
client.LastTime = time.Now()
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
client = &AppClient{
|
||||
Mac: mac,
|
||||
Conn: ws,
|
||||
DeviceId: deviceId,
|
||||
mu: &sync.RWMutex{},
|
||||
LastTime: time.Now(),
|
||||
}
|
||||
addAppClient(client)
|
||||
}
|
||||
logger.Info(ctx, "There is an App connected to the service.", client.Mac)
|
||||
|
||||
// check StackChan status
|
||||
stackChanClient := getStackChanClient(client.Mac)
|
||||
if stackChanClient == nil {
|
||||
offlineMsg := createStringMessage(DeviceOffline, "Your StackChan is offline.")
|
||||
msgType := websocket.BinaryMessage
|
||||
forwardMessage(ctx, client.Conn, &msgType, offlineMsg, client.mu)
|
||||
} else {
|
||||
onlineMsg := createStringMessage(DeviceOnline, "Your StackChan has been launched.")
|
||||
msgType := websocket.BinaryMessage
|
||||
forwardMessage(ctx, client.Conn, &msgType, onlineMsg, client.mu)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
logger.Info(ctx, "There is an App that has disconnected.", mac, deviceType)
|
||||
}()
|
||||
for {
|
||||
messageType, msg, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
var ne net.Error
|
||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
|
||||
logger.Infof(ctx, "App Normal disconnection: mac=%s, deviceType=%s, Error=%v", mac, deviceType, err)
|
||||
break
|
||||
}
|
||||
if errors.As(err, &ne) && ne.Temporary() {
|
||||
logger.Infof(ctx, "App Temporary network error. Continue reading.: mac=%s,deviceType=%s,Error=%v", mac, deviceType, err)
|
||||
continue
|
||||
}
|
||||
if errors.As(err, &ne) && ne.Timeout() {
|
||||
logger.Infof(ctx, "App Timeout disconnection: mac=%s, deviceType=%s", mac, deviceType)
|
||||
break
|
||||
}
|
||||
logger.Errorf(ctx, "App Abnormal disconnection: mac=%s, deviceType=%s, Error=%v", mac, deviceType, err)
|
||||
break
|
||||
}
|
||||
client.LastTime = time.Now()
|
||||
readAppClientMessage(ctx, client, &messageType, &msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addStackChenClient adds a StackChan client to the connection pool and ensures the MAC is registered
|
||||
func addStackChenClient(ctx context.Context, c *StackChanClient) {
|
||||
stackChanClientPool.Store(c.Mac, c)
|
||||
_, _ = service.CreateMacIfNotExists(ctx, c.Mac)
|
||||
}
|
||||
|
||||
// addAppClient adds an App client to the App connection pool (multiple Apps per MAC allowed)
|
||||
func addAppClient(c *AppClient) {
|
||||
val, _ := appClientPool.Load(c.Mac)
|
||||
var clients []*AppClient
|
||||
if val == nil {
|
||||
clients = []*AppClient{c}
|
||||
} else {
|
||||
clients = val.([]*AppClient)
|
||||
clients = append(clients, c)
|
||||
}
|
||||
appClientPool.Store(c.Mac, clients)
|
||||
}
|
||||
|
||||
// getAppClients gets all App clients for the specified MAC address
|
||||
func getAppClients(mac string) []*AppClient {
|
||||
if val, ok := appClientPool.Load(mac); ok {
|
||||
return val.([]*AppClient)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getStackChanClient gets the StackChan client corresponding to the specified MAC address
|
||||
func getStackChanClient(mac string) *StackChanClient {
|
||||
if val, ok := stackChanClientPool.Load(mac); ok {
|
||||
return val.(*StackChanClient)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseBinaryMessage parses a custom binary protocol message, returns type, length, payload, and success status
|
||||
func parseBinaryMessage(ctx context.Context, msg *[]byte) (byte, int, []byte, bool) {
|
||||
if len(*msg) < 1+4 {
|
||||
logger.Warning(ctx, "Message too short, cannot parse header, message not forwarded")
|
||||
return 0, 0, nil, false
|
||||
}
|
||||
|
||||
msgType := (*msg)[0]
|
||||
dataLen := int(binary.BigEndian.Uint32((*msg)[1:5]))
|
||||
payload := (*msg)[5 : 5+dataLen]
|
||||
|
||||
if len(*msg)-5 != dataLen {
|
||||
logger.Warningf(ctx, "Length mismatch: header says %d, actual is %d, message not forwarded", dataLen, len(*msg)-5)
|
||||
return 0, 0, nil, false
|
||||
}
|
||||
|
||||
return msgType, dataLen, payload, true
|
||||
}
|
||||
|
||||
// StartPingTime sends Ping messages to all connected clients for heartbeat detection
|
||||
func StartPingTime(ctx context.Context) {
|
||||
message := createMessage(ping, nil)
|
||||
messageType := websocket.BinaryMessage
|
||||
|
||||
// Iterate over StackChanClientPool
|
||||
stackChanClientPool.Range(func(_, value any) bool {
|
||||
client := value.(*StackChanClient)
|
||||
forwardMessage(ctx, client.Conn, &messageType, message, client.mu)
|
||||
return true // continue iteration
|
||||
})
|
||||
|
||||
// Iterate over AppClientPool
|
||||
appClientPool.Range(func(_, value any) bool {
|
||||
clients := value.([]*AppClient)
|
||||
for _, client := range clients {
|
||||
forwardMessage(ctx, client.Conn, &messageType, message, client.mu)
|
||||
}
|
||||
return true // continue iteration
|
||||
})
|
||||
}
|
||||
|
||||
// CheckExpiredLinks checks and cleans up App client connections that have been inactive for over 60 seconds
|
||||
func CheckExpiredLinks(ctx context.Context) {
|
||||
now := time.Now()
|
||||
var expiredClients []*AppClient
|
||||
|
||||
// First, iterate over AppClientPool
|
||||
appClientPool.Range(func(mac, value any) bool {
|
||||
clients := value.([]*AppClient)
|
||||
newClients := clients[:0]
|
||||
for _, client := range clients {
|
||||
if now.Sub(client.LastTime) > time.Second*15 {
|
||||
// Found expired client
|
||||
// Iterate over StackChanClientPool to clean up CallAppClient and CameraSubscriptionList
|
||||
stackChanClientPool.Range(func(_, scValue any) bool {
|
||||
stackChanClient, ok := scValue.(*StackChanClient)
|
||||
stackChanClient.mu.Lock()
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
// Clean up CallAppClient
|
||||
if stackChanClient.CallAppClient == client {
|
||||
stackChanClient.CallAppClient = nil
|
||||
}
|
||||
|
||||
// Update camera subscription list
|
||||
newCamera := stackChanClient.CameraSubscriptionList[:0]
|
||||
removedCamera := false
|
||||
for _, sub := range stackChanClient.CameraSubscriptionList {
|
||||
if sub != client {
|
||||
newCamera = append(newCamera, sub)
|
||||
} else {
|
||||
removedCamera = true
|
||||
}
|
||||
}
|
||||
stackChanClient.CameraSubscriptionList = newCamera
|
||||
stackChanClient.mu.Unlock()
|
||||
if removedCamera && len(newCamera) == 0 {
|
||||
msg := createMessage(OffCamera, nil)
|
||||
msgType := websocket.BinaryMessage
|
||||
forwardMessage(ctx, stackChanClient.Conn, &msgType, msg, stackChanClient.mu)
|
||||
}
|
||||
return true
|
||||
})
|
||||
expiredClients = append(expiredClients, client)
|
||||
} else {
|
||||
newClients = append(newClients, client)
|
||||
}
|
||||
}
|
||||
if len(newClients) == 0 {
|
||||
appClientPool.Delete(mac)
|
||||
} else {
|
||||
appClientPool.Store(mac, newClients)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
for _, client := range expiredClients {
|
||||
logger.Infof(ctx, "Kicked out an expired App client: %s", client.Mac)
|
||||
err := client.Conn.Close()
|
||||
if err != nil {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readStackChanMessage handles messages from the StackChan device side
|
||||
func readStackChanMessage(ctx context.Context, client *StackChanClient, messageType *int, msg *[]byte) {
|
||||
if *messageType == websocket.BinaryMessage {
|
||||
msgType, _, _, ok := parseBinaryMessage(ctx, msg)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
switch msgType {
|
||||
case pong:
|
||||
break
|
||||
case ControlAvatar, ControlMotion, OnCamera, OffCamera:
|
||||
break
|
||||
case RefuseCall:
|
||||
// Refused call, remove and notify appClient
|
||||
appClient := client.CallAppClient
|
||||
if appClient != nil {
|
||||
forwardMessage(ctx, appClient.Conn, messageType, msg, appClient.mu)
|
||||
client.mu.Lock()
|
||||
client.CallAppClient = nil
|
||||
client.mu.Unlock()
|
||||
}
|
||||
break
|
||||
case AgreeCall:
|
||||
// Agreed to call
|
||||
appClient := client.CallAppClient
|
||||
if appClient != nil {
|
||||
forwardMessage(ctx, appClient.Conn, messageType, msg, appClient.mu)
|
||||
client.mu.Lock()
|
||||
client.CameraSubscriptionList = append(client.CameraSubscriptionList, appClient)
|
||||
client.mu.Unlock()
|
||||
if len(client.CameraSubscriptionList) == 1 {
|
||||
onMsg := createMessage(OnCamera, nil)
|
||||
onType := websocket.BinaryMessage
|
||||
forwardMessage(ctx, client.Conn, &onType, onMsg, client.mu)
|
||||
}
|
||||
}
|
||||
break
|
||||
case HangupCall:
|
||||
// Hang up call
|
||||
appClient := client.CallAppClient
|
||||
if appClient != nil {
|
||||
forwardMessage(ctx, appClient.Conn, messageType, msg, appClient.mu)
|
||||
// Remove the client from the subscription list
|
||||
newList := client.CameraSubscriptionList[:0]
|
||||
for _, subClient := range client.CameraSubscriptionList {
|
||||
if subClient != appClient {
|
||||
newList = append(newList, subClient)
|
||||
}
|
||||
}
|
||||
client.mu.Lock()
|
||||
client.CameraSubscriptionList = newList
|
||||
client.mu.Unlock()
|
||||
// If the subscription list is empty, notify to turn off the camera
|
||||
if len(client.CameraSubscriptionList) == 0 {
|
||||
offMsg := createMessage(OffCamera, nil)
|
||||
offType := websocket.BinaryMessage
|
||||
forwardMessage(ctx, client.Conn, &offType, offMsg, client.mu)
|
||||
}
|
||||
}
|
||||
break
|
||||
case GetDeviceName:
|
||||
// Query device name
|
||||
name, err := service.GetDeviceName(ctx, client.Mac)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if name == "" {
|
||||
logger.Infof(ctx, "Queried device nickname is empty")
|
||||
return
|
||||
}
|
||||
newMsg := createStringMessage(GetDeviceName, name)
|
||||
forwardMessage(ctx, client.Conn, messageType, newMsg, client.mu)
|
||||
break
|
||||
case Opus:
|
||||
|
||||
break
|
||||
case Jpeg:
|
||||
subscribers := client.CameraSubscriptionList
|
||||
if len(subscribers) > 0 {
|
||||
var isAll = true
|
||||
for _, subClient := range subscribers {
|
||||
if subClient.Conn != nil {
|
||||
isAll = false
|
||||
}
|
||||
forwardMessage(ctx, subClient.Conn, messageType, msg, subClient.mu)
|
||||
}
|
||||
if isAll {
|
||||
msg = createMessage(OffCamera, nil)
|
||||
forwardMessage(ctx, client.Conn, messageType, msg, client.mu)
|
||||
}
|
||||
} else {
|
||||
msg = createMessage(OffCamera, nil)
|
||||
forwardMessage(ctx, client.Conn, messageType, msg, client.mu)
|
||||
}
|
||||
break
|
||||
case GetAvatarPosture:
|
||||
appClients := getAppClients(client.Mac)
|
||||
for _, appClient := range appClients {
|
||||
forwardMessage(ctx, appClient.Conn, messageType, msg, appClient.mu)
|
||||
}
|
||||
break
|
||||
default:
|
||||
logger.Infof(ctx, "Unknown binary msgType: %d", msgType)
|
||||
appClients := getAppClients(client.Mac)
|
||||
if appClients != nil {
|
||||
for _, appClient := range appClients {
|
||||
forwardMessage(ctx, appClient.Conn, messageType, msg, appClient.mu)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if *messageType == websocket.TextMessage {
|
||||
appClients := getAppClients(client.Mac)
|
||||
if appClients != nil {
|
||||
for _, appClient := range appClients {
|
||||
forwardMessage(ctx, appClient.Conn, messageType, msg, appClient.mu)
|
||||
}
|
||||
}
|
||||
} else if *messageType == websocket.PingMessage {
|
||||
logger.Info(ctx, "Received ping message from StackChan side")
|
||||
}
|
||||
}
|
||||
|
||||
// readAppClientMessage handles messages from the App side
|
||||
func readAppClientMessage(ctx context.Context, client *AppClient, messageType *int, msg *[]byte) {
|
||||
if *messageType == websocket.BinaryMessage {
|
||||
msgType, _, payload, ok := parseBinaryMessage(ctx, msg)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
switch msgType {
|
||||
case pong:
|
||||
break
|
||||
case GetDeviceName:
|
||||
// Query device name
|
||||
name, err := service.GetDeviceName(ctx, client.Mac)
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
if name == "" {
|
||||
logger.Infof(ctx, "Queried device nickname is empty")
|
||||
return
|
||||
}
|
||||
newMsg := createStringMessage(GetDeviceName, name)
|
||||
logger.Infof(ctx, "Device name found, returning: "+name)
|
||||
forwardMessage(ctx, client.Conn, messageType, newMsg, client.mu)
|
||||
break
|
||||
case UpdateDeviceName:
|
||||
stackChanClient := getStackChanClient(client.Mac)
|
||||
if stackChanClient != nil {
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, msg, stackChanClient.mu)
|
||||
}
|
||||
appClients := getAppClients(client.Mac)
|
||||
for _, appClient := range appClients {
|
||||
forwardMessage(ctx, appClient.Conn, messageType, msg, appClient.mu)
|
||||
}
|
||||
break
|
||||
case Opus:
|
||||
break
|
||||
case Jpeg:
|
||||
if len(payload) < 12 {
|
||||
logger.Warningf(ctx, "Payload too short, cannot parse MAC address: %v", payload)
|
||||
return
|
||||
}
|
||||
macAddrBytes := payload[:12]
|
||||
data := payload[12:]
|
||||
macAddr := string(macAddrBytes)
|
||||
newMsg := createMessage(msgType, data)
|
||||
stackChanClient := getStackChanClient(macAddr)
|
||||
if stackChanClient != nil {
|
||||
if stackChanClient.phoneScreen {
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, newMsg, stackChanClient.mu)
|
||||
}
|
||||
}
|
||||
break
|
||||
case ControlAvatar, ControlMotion:
|
||||
if len(payload) < 12 {
|
||||
logger.Warningf(ctx, "Payload too short, cannot parse MAC address: %v", payload)
|
||||
return
|
||||
}
|
||||
macAddrBytes := payload[:12]
|
||||
data := payload[12:]
|
||||
macAddr := string(macAddrBytes)
|
||||
newMsg := createMessage(msgType, data)
|
||||
stackChanClient := getStackChanClient(macAddr)
|
||||
if stackChanClient != nil {
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, newMsg, stackChanClient.mu)
|
||||
} else {
|
||||
logger.Infof(ctx, "StackChan is currently offline")
|
||||
}
|
||||
break
|
||||
case TextMessage:
|
||||
if len(payload) < 12 {
|
||||
logger.Warningf(ctx, "Payload too short, cannot parse MAC address: %v", payload)
|
||||
return
|
||||
}
|
||||
macAddr := string(payload[:12])
|
||||
data := payload[12:]
|
||||
newMsg := createMessage(msgType, data)
|
||||
stackChanClient := getStackChanClient(macAddr)
|
||||
if stackChanClient != nil {
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, newMsg, stackChanClient.mu)
|
||||
}
|
||||
appClients := getAppClients(macAddr)
|
||||
if appClients != nil {
|
||||
for _, appClient := range appClients {
|
||||
forwardMessage(ctx, appClient.Conn, messageType, newMsg, appClient.mu)
|
||||
}
|
||||
}
|
||||
break
|
||||
case RequestCall:
|
||||
// Request call
|
||||
if len(payload) < 12 {
|
||||
logger.Warningf(ctx, "Payload too short, cannot parse MAC address: %v", payload)
|
||||
return
|
||||
}
|
||||
macAddr := string(payload[:12])
|
||||
data := payload[12:]
|
||||
stackChanClient := getStackChanClient(macAddr)
|
||||
if stackChanClient != nil {
|
||||
stackChanClient.mu.Lock()
|
||||
if stackChanClient.CallAppClient == nil || stackChanClient.CallAppClient == client {
|
||||
stackChanClient.CallAppClient = client
|
||||
stackChanClient.mu.Unlock()
|
||||
newMsg := createMessage(msgType, data)
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, newMsg, stackChanClient.mu)
|
||||
} else {
|
||||
stackChanClient.mu.Unlock()
|
||||
// Notify App that the other side is already in a call
|
||||
newMsg := createStringMessage(inCall, "The other party is currently in a call")
|
||||
forwardMessage(ctx, client.Conn, messageType, newMsg, client.mu)
|
||||
}
|
||||
}
|
||||
break
|
||||
case HangupCall:
|
||||
stackChanClientPool.Range(func(_, value any) bool {
|
||||
stackChanClient := value.(*StackChanClient)
|
||||
if stackChanClient.CallAppClient == client {
|
||||
// Found corresponding call
|
||||
stackChanClient.mu.Lock()
|
||||
stackChanClient.CallAppClient = nil
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, msg, stackChanClient.mu)
|
||||
|
||||
newList := stackChanClient.CameraSubscriptionList[:0]
|
||||
for _, sub := range stackChanClient.CameraSubscriptionList {
|
||||
if sub != client {
|
||||
newList = append(newList, sub)
|
||||
}
|
||||
}
|
||||
stackChanClient.CameraSubscriptionList = newList
|
||||
stackChanClient.mu.Unlock()
|
||||
if len(stackChanClient.CameraSubscriptionList) == 0 {
|
||||
offMsg := createMessage(OffCamera, nil)
|
||||
offType := websocket.BinaryMessage
|
||||
forwardMessage(ctx, stackChanClient.Conn, &offType, offMsg, stackChanClient.mu)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
break
|
||||
case OnCamera:
|
||||
macAddr := string(payload)
|
||||
stackChanClient := getStackChanClient(macAddr)
|
||||
if stackChanClient != nil {
|
||||
stackChanClient.mu.Lock()
|
||||
alreadySubscribed := false
|
||||
for _, sub := range stackChanClient.CameraSubscriptionList {
|
||||
if sub == client {
|
||||
alreadySubscribed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alreadySubscribed {
|
||||
stackChanClient.CameraSubscriptionList = append(stackChanClient.CameraSubscriptionList, client)
|
||||
stackChanClient.mu.Unlock()
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, msg, stackChanClient.mu)
|
||||
} else {
|
||||
stackChanClient.mu.Unlock()
|
||||
}
|
||||
}
|
||||
break
|
||||
case OffCamera:
|
||||
macAddr := string(payload)
|
||||
stackChanClient := getStackChanClient(macAddr)
|
||||
if stackChanClient != nil {
|
||||
stackChanClient.mu.Lock()
|
||||
existed := false
|
||||
newList := stackChanClient.CameraSubscriptionList[:0]
|
||||
for _, subClient := range stackChanClient.CameraSubscriptionList {
|
||||
if subClient == client {
|
||||
existed = true
|
||||
} else {
|
||||
newList = append(newList, subClient)
|
||||
}
|
||||
}
|
||||
shouldNotify := existed && len(newList) == 0
|
||||
stackChanClient.CameraSubscriptionList = newList
|
||||
stackChanClient.mu.Unlock()
|
||||
if shouldNotify {
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, msg, stackChanClient.mu)
|
||||
}
|
||||
}
|
||||
break
|
||||
case OnPhoneScreen:
|
||||
// Show phone screen
|
||||
macAddr := string(payload)
|
||||
stackChanClient := getStackChanClient(macAddr)
|
||||
if stackChanClient != nil {
|
||||
stackChanClient.mu.Lock()
|
||||
if stackChanClient.phoneScreen == false {
|
||||
stackChanClient.phoneScreen = true
|
||||
stackChanClient.mu.Unlock()
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, msg, stackChanClient.mu)
|
||||
} else {
|
||||
stackChanClient.mu.Unlock()
|
||||
}
|
||||
}
|
||||
break
|
||||
case OffPhoneScreen:
|
||||
// Hide phone screen
|
||||
macAddr := string(payload)
|
||||
stackChanClient := getStackChanClient(macAddr)
|
||||
if stackChanClient != nil {
|
||||
stackChanClient.mu.Lock()
|
||||
if stackChanClient.phoneScreen == true {
|
||||
stackChanClient.phoneScreen = false
|
||||
stackChanClient.mu.Unlock()
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, msg, stackChanClient.mu)
|
||||
} else {
|
||||
stackChanClient.mu.Unlock()
|
||||
}
|
||||
}
|
||||
break
|
||||
case Dance:
|
||||
// Dance message
|
||||
stackChanClient := getStackChanClient(client.Mac)
|
||||
if stackChanClient != nil {
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, msg, stackChanClient.mu)
|
||||
}
|
||||
break
|
||||
case GetAvatarPosture:
|
||||
stackChanClient := getStackChanClient(client.Mac)
|
||||
if stackChanClient != nil {
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, msg, stackChanClient.mu)
|
||||
}
|
||||
default:
|
||||
logger.Infof(ctx, "Unknown binary msgType: %d", msgType)
|
||||
stackChanClient := getStackChanClient(client.Mac)
|
||||
if stackChanClient != nil {
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, msg, stackChanClient.mu)
|
||||
}
|
||||
}
|
||||
} else if *messageType == websocket.TextMessage {
|
||||
// Directly forward other message types
|
||||
stackChanClient := getStackChanClient(client.Mac)
|
||||
if stackChanClient != nil {
|
||||
forwardMessage(ctx, stackChanClient.Conn, messageType, msg, stackChanClient.mu)
|
||||
}
|
||||
} else if *messageType == websocket.PingMessage {
|
||||
logger.Info(ctx, "Received ping message from App side")
|
||||
}
|
||||
}
|
||||
|
||||
// forwardMessage forwards a message to the specified connection, with mutex for concurrency safety
|
||||
func forwardMessage(ctx context.Context, conn *websocket.Conn, messageType *int, msg *[]byte, mu *sync.RWMutex) {
|
||||
if conn == nil {
|
||||
logger.Infof(ctx, "StackChan is currently offline")
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
err := conn.WriteMessage(*messageType, *msg)
|
||||
if err != nil {
|
||||
//logger.Info(ctx, "Message forwarding failed: %v", err)
|
||||
} else {
|
||||
//logger.Info(ctx, "Message sent successfully")
|
||||
}
|
||||
}
|
||||
|
||||
// createMessage encapsulates a binary message according to custom protocol (type + length + data)
|
||||
func createMessage(msgType byte, data []byte) *[]byte {
|
||||
var dataLen int
|
||||
if data != nil {
|
||||
dataLen = len(data)
|
||||
} else {
|
||||
dataLen = 0
|
||||
}
|
||||
msg := make([]byte, 1+4+dataLen)
|
||||
msg[0] = msgType
|
||||
binary.BigEndian.PutUint32(msg[1:5], uint32(dataLen))
|
||||
if dataLen > 0 {
|
||||
copy(msg[5:], data)
|
||||
}
|
||||
return &msg
|
||||
}
|
||||
|
||||
// createStringMessage creates a binary message with a string payload
|
||||
func createStringMessage(msgType byte, data string) *[]byte {
|
||||
return createMessage(msgType, []byte(data))
|
||||
}
|
||||
|
||||
// GetRandomStackChanDevice get Random StackChan Device list
|
||||
func GetRandomStackChanDevice(userMac string, maxLength int) (list []string) {
|
||||
if maxLength <= 0 {
|
||||
return []string{}
|
||||
}
|
||||
var macs []string
|
||||
|
||||
stackChanClientPool.Range(func(key, value interface{}) bool {
|
||||
mac := key.(string)
|
||||
client := value.(*StackChanClient)
|
||||
|
||||
if mac == userMac {
|
||||
return true
|
||||
}
|
||||
|
||||
client.mu.RLock()
|
||||
online := client.Conn != nil
|
||||
client.mu.RUnlock()
|
||||
|
||||
if online {
|
||||
macs = append(macs, mac)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if len(macs) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
r.Shuffle(len(macs), func(i, j int) {
|
||||
macs[i], macs[j] = macs[j], macs[i]
|
||||
})
|
||||
|
||||
if len(macs) > maxLength {
|
||||
macs = macs[:maxLength]
|
||||
}
|
||||
|
||||
return macs
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"stackChan/internal/cmd"
|
||||
_ "stackChan/internal/packed"
|
||||
|
||||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Main.Run(gctx.GetInitCtx())
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# https://goframe.org/docs/web/server-config-file-template
|
||||
server:
|
||||
address: ":12800"
|
||||
# openapiPath: "/api.json"
|
||||
swaggerPath: "/swagger"
|
||||
|
||||
# https://goframe.org/docs/core/glog-config
|
||||
logger:
|
||||
level : "all"
|
||||
stdout: true
|
||||
|
||||
# https://goframe.org/docs/core/gdb-config-file
|
||||
database:
|
||||
default:
|
||||
link: "sqlite::@file(/stackChan.sqlite)"
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: template-single
|
||||
labels:
|
||||
app: template-single
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: template-single
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: template-single
|
||||
spec:
|
||||
containers:
|
||||
- name : main
|
||||
image: template-single
|
||||
imagePullPolicy: Always
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: template-single
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8000
|
||||
selector:
|
||||
app: template-single
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: template-single-configmap
|
||||
data:
|
||||
config.yaml: |
|
||||
server:
|
||||
address: ":8000"
|
||||
openapiPath: "/api.json"
|
||||
swaggerPath: "/swagger"
|
||||
|
||||
logger:
|
||||
level : "all"
|
||||
stdout: true
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: template-single
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name : main
|
||||
image: template-single:develop
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
- configmap.yaml
|
||||
|
||||
patchesStrategicMerge:
|
||||
- deployment.yaml
|
||||
|
||||
namespace: default
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM alpine:latest
|
||||
ENV WORKDIR=/app
|
||||
WORKDIR $WORKDIR
|
||||
COPY ./stackChan $WORKDIR/stackChan
|
||||
COPY ./config.yaml $WORKDIR/config.yaml
|
||||
RUN chmod +x $WORKDIR/stackChan
|
||||
CMD ["./stackChan"]
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This shell is executed before docker build.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user