mirror of
https://github.com/m5stack/StackChan.git
synced 2026-04-27 19:12:40 +00:00
server code 4/27
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package boot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/web_socket"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func InitCron() {
|
||||
startPingTimer()
|
||||
startCleanTimer()
|
||||
}
|
||||
|
||||
var (
|
||||
pingTimerStarted atomic.Bool
|
||||
cleanTimerStarted atomic.Bool
|
||||
)
|
||||
|
||||
// startPingTimer starts the heartbeat timer, with panic recovery and restart logic.
|
||||
func startPingTimer() {
|
||||
if !pingTimerStarted.CompareAndSwap(false, true) {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
_ = cancel // for future use
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
g.Log().Info(ctx, "The heartbeat sending timer has been activated")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
pingTimerStarted.Store(false)
|
||||
return
|
||||
case <-ticker.C:
|
||||
func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
g.Log().Errorf(ctx, "Heartbeat sending task crash: %v, the timer is about to be restarted", err)
|
||||
pingTimerStarted.Store(false)
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
startPingTimer()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
web_socket.StartPingTime(ctx)
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// startCleanTimer starts the connection cleaning timer, with panic recovery and restart logic.
|
||||
func startCleanTimer() {
|
||||
if !cleanTimerStarted.CompareAndSwap(false, true) {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
_ = cancel // for future use
|
||||
go func() {
|
||||
ticker := time.NewTicker(15 * time.Second)
|
||||
defer ticker.Stop()
|
||||
g.Log().Info(ctx, "The connection cleaning timer has been started")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
cleanTimerStarted.Store(false)
|
||||
return
|
||||
case <-ticker.C:
|
||||
func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
g.Log().Errorf(ctx, "Connection cleanup task crash: %v, about to restart the timer", err)
|
||||
cleanTimerStarted.Store(false)
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
startCleanTimer()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
web_socket.CheckExpiredLinks(ctx)
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
+33
-34
@@ -7,23 +7,27 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"stackChan/internal/boot"
|
||||
"stackChan/internal/controller/admin"
|
||||
"stackChan/internal/controller/appstore"
|
||||
"stackChan/internal/controller/dance"
|
||||
"stackChan/internal/controller/device"
|
||||
"stackChan/internal/controller/file"
|
||||
"stackChan/internal/controller/friend"
|
||||
"stackChan/internal/controller/pano"
|
||||
"stackChan/internal/controller/post"
|
||||
"stackChan/internal/controller/stackchandevice"
|
||||
"stackChan/internal/controller/user"
|
||||
"stackChan/internal/controller/xiaozhi"
|
||||
"stackChan/internal/middleware"
|
||||
"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 (
|
||||
@@ -32,20 +36,16 @@ var (
|
||||
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.SetClientMaxBodySize(100 * 1024 * 1024)
|
||||
|
||||
s.Use(middleware.CORS)
|
||||
|
||||
s.BindHandler("/stackChan/ws", web_socket.Handler)
|
||||
|
||||
// heartBeat
|
||||
boot.InitCron()
|
||||
|
||||
///Configuration file access
|
||||
s.Group("/file", func(group *ghttp.RouterGroup) {
|
||||
group.GET("/*filepath", func(r *ghttp.Request) {
|
||||
@@ -65,28 +65,27 @@ var (
|
||||
})
|
||||
})
|
||||
|
||||
s.Group("/stackChan", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(ghttp.MiddlewareHandlerResponse)
|
||||
group.Bind(device.NewV1(), friend.NewV1(), dance.NewV1(), file.NewV1(), post.NewV1())
|
||||
s.Group("/stackChan/v2", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(middleware.V2TokenAuthMiddleware, ghttp.MiddlewareHandlerResponse)
|
||||
group.Bind(user.NewV2(), dance.NewV2(), device.NewV2())
|
||||
})
|
||||
|
||||
s.Group("/stackChan", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(middleware.TokenAuthMiddleware, ghttp.MiddlewareHandlerResponse)
|
||||
group.Bind(device.NewV1(), friend.NewV1(), dance.NewV1(), file.NewV1(), post.NewV1(), pano.NewV1(), appstore.NewV1(), xiaozhi.NewV1(), stackchandevice.NewV2())
|
||||
})
|
||||
|
||||
s.Group("/admin/stackChan", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(middleware.AdminTokenAuthMiddleware, ghttp.MiddlewareHandlerResponse)
|
||||
group.Bind(admin.NewV1(), file.NewV1())
|
||||
})
|
||||
|
||||
// Do not use SetServerRoot, globally only provide frontend entry via /web
|
||||
//s.SetServerRoot("web/management")
|
||||
|
||||
s.SetPort(12800)
|
||||
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 admin
|
||||
@@ -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 admin
|
||||
|
||||
import (
|
||||
"stackChan/api/admin"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() admin.IAdminV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/api/admin/v1"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) AddApp(ctx context.Context, req *v1.AddAppReq) (res *v1.AddAppRes, err error) {
|
||||
app := do.AppStore{
|
||||
AppName: req.AppName,
|
||||
AppIconUrl: req.AppIconUrl,
|
||||
Description: req.Description,
|
||||
FirmwareUrl: req.FirmwareUrl,
|
||||
}
|
||||
id, err := dao.AppStore.Ctx(ctx).Data(&app).InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeInternalError, err, "Failed to insert app")
|
||||
}
|
||||
var appInfo model.AppInfo
|
||||
err = dao.AppStore.Ctx(ctx).Where("id", id).Scan(&appInfo)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeInternalError, err, "Failed to fetch inserted app")
|
||||
}
|
||||
res = (*v1.AddAppRes)(&appInfo)
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/service"
|
||||
|
||||
"stackChan/api/admin/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) AdminLogin(ctx context.Context, req *v1.AdminLoginReq) (res *v1.AdminLoginRes, err error) {
|
||||
return service.AdminLogin(ctx, req)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"stackChan/api/admin/v1"
|
||||
"stackChan/internal/dao"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) DeleteApp(ctx context.Context, req *v1.DeleteAppReq) (res *v1.DeleteAppRes, err error) {
|
||||
_, err = dao.AppStore.Ctx(ctx).Where("id", req.Id).Data(g.Map{"is_deleted": 1}).Update()
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeInternalError, err, "Failed to delete app")
|
||||
}
|
||||
|
||||
res = &v1.DeleteAppRes{}
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/service"
|
||||
|
||||
"stackChan/api/admin/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetAppList(ctx context.Context, req *v1.GetAppListReq) (res *v1.GetAppListRes, err error) {
|
||||
apps, err := service.GetAppList(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := v1.GetAppListRes(apps)
|
||||
return &response, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/model"
|
||||
|
||||
"stackChan/api/admin/v1"
|
||||
"stackChan/internal/dao"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) UpdateApp(ctx context.Context, req *v1.UpdateAppReq) (res *v1.UpdateAppRes, err error) {
|
||||
updateData := g.Map{}
|
||||
if req.AppName != "" {
|
||||
updateData["app_name"] = req.AppName
|
||||
}
|
||||
if req.AppIconUrl != "" {
|
||||
updateData["app_icon_url"] = req.AppIconUrl
|
||||
}
|
||||
if req.Description != "" {
|
||||
updateData["description"] = req.Description
|
||||
}
|
||||
if req.FirmwareUrl != "" {
|
||||
updateData["firmware_url"] = req.FirmwareUrl
|
||||
}
|
||||
|
||||
if len(updateData) > 0 {
|
||||
_, err = dao.AppStore.Ctx(ctx).WherePri(req.Id).Data(updateData).Update()
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeInternalError, err, "Failed to update app")
|
||||
}
|
||||
}
|
||||
|
||||
var appInfo model.AppInfo
|
||||
err = dao.AppStore.Ctx(ctx).WherePri(req.Id).Scan(&appInfo)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeInternalError, err, "Failed to fetch updated app")
|
||||
}
|
||||
|
||||
res = (*v1.UpdateAppRes)(&appInfo)
|
||||
return res, 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 appstore
|
||||
@@ -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 appstore
|
||||
|
||||
import (
|
||||
"stackChan/api/appstore"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() appstore.IAppstoreV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package appstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/service"
|
||||
|
||||
"stackChan/api/appstore/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetAppList(ctx context.Context, req *v1.GetAppListReq) (res *v1.GetAppListRes, err error) {
|
||||
apps, err := service.GetAppList(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := v1.GetAppListRes(apps)
|
||||
return &response, nil
|
||||
}
|
||||
@@ -17,3 +17,9 @@ type ControllerV1 struct{}
|
||||
func NewV1() dance.IDanceV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
|
||||
type ControllerV2 struct{}
|
||||
|
||||
func NewV2() dance.IDanceV2 {
|
||||
return &ControllerV2{}
|
||||
}
|
||||
|
||||
@@ -7,60 +7,75 @@ package dance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"stackChan/api/dance/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
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")
|
||||
// 1. Get and validate MAC address (business required parameter)
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "MAC address cannot be empty")
|
||||
}
|
||||
|
||||
device, err := dao.Device.Ctx(ctx).Where("mac=", req.Mac).One()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// 2. Auto validate using struct v tag (DanceName required), manual secondary validation as fallback
|
||||
if req.DanceName == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "Dance name cannot be empty")
|
||||
}
|
||||
|
||||
if device.IsEmpty() {
|
||||
_, err = dao.Device.Ctx(ctx).Data(dao.Device.Columns().Mac, req.Mac).Insert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// 3. Validate dance data not empty (RawMessage need to check if empty/only null)
|
||||
if len(req.DanceData) == 0 || string(req.DanceData) == "null" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "Dance data cannot be empty or null")
|
||||
}
|
||||
// 4. Use transaction to ensure data consistency
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 4.1 Query device, create if not exists
|
||||
device, err := dao.Device.Ctx(ctx).TX(tx).Where("mac=?", mac).One()
|
||||
if err != nil && !gerror.HasCode(err, gcode.CodeNotFound) {
|
||||
return gerror.NewCode(gcode.CodeInternalError, "Failed to query device: %v", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
dance, err := dao.DeviceDance.Ctx(ctx).Where("mac=?", req.Mac).Where("dance_index=?", req.Index).One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create device if not exists
|
||||
if device.IsEmpty() {
|
||||
_, err = dao.Device.Ctx(ctx).TX(tx).Data(dao.Device.Columns().Mac, mac).Insert()
|
||||
if err != nil {
|
||||
return gerror.NewCode(gcode.CodeInternalError, "Failed to create device: %v", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
danceListJSON, err := json.Marshal(req.List)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 4.2 Check if dance data with same MAC+DanceName exists (avoid duplicates)
|
||||
exist, err := dao.DeviceDance.Ctx(ctx).TX(tx).
|
||||
Where("mac=?", mac).
|
||||
Where("dance_name=?", req.DanceName).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return gerror.NewCode(gcode.CodeInternalError, "Failed to check duplicate dance data: %v", err.Error())
|
||||
}
|
||||
if exist {
|
||||
return gerror.NewCode(gcode.CodeBusinessValidationFailed, "Dance data with MAC %s and name '%s' already exists", mac, req.DanceName)
|
||||
}
|
||||
|
||||
if dance.IsEmpty() {
|
||||
_, err = dao.DeviceDance.Ctx(ctx).Data(do.DeviceDance{
|
||||
Mac: req.Mac,
|
||||
DanceIndex: req.Index,
|
||||
DanceData: danceListJSON,
|
||||
// 4.3 Insert dance data (use RawMessage directly, no need for secondary serialization)
|
||||
_, err = dao.DeviceDance.Ctx(ctx).TX(tx).Data(do.DeviceDance{
|
||||
Mac: mac,
|
||||
DanceData: req.DanceData, // Use RawMessage directly, avoid duplicate marshal
|
||||
DanceName: req.DanceName,
|
||||
MusicUrl: req.MusicUrl, // Add background music URL field
|
||||
}).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
|
||||
return gerror.NewCode(gcode.CodeInternalError, "Failed to insert dance data: %v", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := v1.CreateRes("Dance data saved successfully")
|
||||
return &response, nil
|
||||
|
||||
@@ -8,11 +8,20 @@ package dance
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
|
||||
"stackChan/api/dance/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
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()
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
_, err = dao.DeviceDance.Ctx(ctx).Where("id=", req.Id).Delete()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package dance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/dance/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetDanceInfo(ctx context.Context, req *v1.GetDanceInfoReq) (res *v1.GetDanceInfoRes, err error) {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
if req.Id == 0 {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "The dance ID cannot be left blank.")
|
||||
}
|
||||
var dance model.Dance
|
||||
err = dao.DeviceDance.Ctx(ctx).Where("mac=?", mac).Where("id=?", req.Id).Scan(&dance)
|
||||
if err != nil {
|
||||
return nil, gerror.NewCode(gcode.CodeInternalError)
|
||||
}
|
||||
return new(v1.GetDanceInfoRes(dance)), nil
|
||||
}
|
||||
@@ -7,38 +7,52 @@ 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"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"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
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
|
||||
var danceList []model.Dance
|
||||
err = dao.DeviceDance.Ctx(ctx).Where(do.DeviceDance{
|
||||
Mac: req.Mac,
|
||||
}).Scan(&list)
|
||||
Mac: mac,
|
||||
}).Scan(&danceList)
|
||||
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)
|
||||
|
||||
// Core modification: insert default data only when query result is empty
|
||||
if len(danceList) == 0 {
|
||||
// Insert single default dance data
|
||||
defaultDance := do.DeviceDance{
|
||||
Mac: mac,
|
||||
MusicUrl: "file/music/stackchan_music.mp3",
|
||||
DanceData: model.DefaultDanceData,
|
||||
}
|
||||
_, err = dao.DeviceDance.Ctx(ctx).Data(defaultDance).Insert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Re-query list (one data exists now)
|
||||
err = dao.DeviceDance.Ctx(ctx).Where(do.DeviceDance{
|
||||
Mac: mac,
|
||||
}).Scan(&danceList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := strconv.Itoa(deviceDance.DanceIndex)
|
||||
danceMap[key] = danceList
|
||||
}
|
||||
response := v1.GetListRes(danceMap)
|
||||
return &response, nil
|
||||
|
||||
return new(v1.GetListRes(danceList)), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package dance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/api/dance/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetMusicList(ctx context.Context, req *v1.GetMusicListReq) (res *v1.GetMusicListRes, err error) {
|
||||
var list = make([]string, 1)
|
||||
list = append(list, "file/music/stackchan_music.mp3")
|
||||
return new(v1.GetMusicListRes(list)), nil
|
||||
}
|
||||
@@ -9,23 +9,42 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"stackChan/api/dance/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) {
|
||||
response := v1.UpdateRes("")
|
||||
danceJSON, err := json.Marshal(req.Data)
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "MAC address cannot be empty")
|
||||
}
|
||||
if req.Id == 0 { // Adjust based on actual type of req.Id (int/string)
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "Dance ID cannot be empty")
|
||||
}
|
||||
updateData := do.DeviceDance{}
|
||||
if req.MusicUrl != "" {
|
||||
updateData.MusicUrl = req.MusicUrl
|
||||
}
|
||||
if req.DanceName != "" {
|
||||
updateData.DanceName = req.DanceName
|
||||
}
|
||||
if req.DanceData != nil {
|
||||
danceJSON, err := json.Marshal(req.DanceData)
|
||||
if err != nil {
|
||||
// Wrap serialization error, add business prompt
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "Dance data serialization failed: %v")
|
||||
}
|
||||
updateData.DanceData = danceJSON
|
||||
}
|
||||
_, err = dao.DeviceDance.Ctx(ctx).Where("mac=?", mac).Where("id=?", req.Id).Data(updateData).Update()
|
||||
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
|
||||
return new(v1.UpdateRes("Update successful")), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package dance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/dance/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) Create(ctx context.Context, req *v2.CreateReq) (res *v2.CreateRes, err error) {
|
||||
mac := req.Mac
|
||||
if req.DanceName == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "Dance name cannot be empty")
|
||||
}
|
||||
if len(req.DanceData) == 0 || string(req.DanceData) == "null" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "Dance data cannot be empty or null")
|
||||
}
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
device, err := dao.Device.Ctx(ctx).TX(tx).Where("mac=?", mac).One()
|
||||
if err != nil && !gerror.HasCode(err, gcode.CodeNotFound) {
|
||||
return gerror.NewCode(gcode.CodeInternalError, "Failed to query device: %v", err.Error())
|
||||
}
|
||||
if device.IsEmpty() {
|
||||
_, err = dao.Device.Ctx(ctx).TX(tx).Data(dao.Device.Columns().Mac, mac).Insert()
|
||||
if err != nil {
|
||||
return gerror.NewCode(gcode.CodeInternalError, "Failed to create device: %v", err.Error())
|
||||
}
|
||||
}
|
||||
exist, err := dao.DeviceDance.Ctx(ctx).TX(tx).
|
||||
Where("mac=?", mac).
|
||||
Where("dance_name=?", req.DanceName).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return gerror.NewCode(gcode.CodeInternalError, "Failed to check duplicate dance data: %v", err.Error())
|
||||
}
|
||||
if exist {
|
||||
return gerror.NewCode(gcode.CodeBusinessValidationFailed, "Dance data with MAC %s and name '%s' already exists", mac, req.DanceName)
|
||||
}
|
||||
_, err = dao.DeviceDance.Ctx(ctx).TX(tx).Data(do.DeviceDance{
|
||||
Mac: mac,
|
||||
DanceData: req.DanceData,
|
||||
DanceName: req.DanceName,
|
||||
MusicUrl: req.MusicUrl,
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return gerror.NewCode(gcode.CodeInternalError, "Failed to insert dance data: %v", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new(v2.CreateRes("Dance data saved successfully")), 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/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) Delete(ctx context.Context, req *v2.DeleteReq) (res *v2.DeleteRes, err error) {
|
||||
_, err = dao.DeviceDance.Ctx(ctx).Where("id=", req.Id).Delete()
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package dance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"stackChan/api/dance/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) GetDanceInfo(ctx context.Context, req *v2.GetDanceInfoReq) (res *v2.GetDanceInfoRes, err error) {
|
||||
if req.Id == 0 {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "The dance ID cannot be left blank.")
|
||||
}
|
||||
var dance model.Dance
|
||||
err = dao.DeviceDance.Ctx(ctx).Where("id=?", req.Id).Scan(&dance)
|
||||
if err != nil {
|
||||
return nil, gerror.NewCode(gcode.CodeInternalError)
|
||||
}
|
||||
return new(v2.GetDanceInfoRes(dance)), nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package dance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/api/dance/v2"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/do"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) GetList(ctx context.Context, req *v2.GetListReq) (res *v2.GetListRes, err error) {
|
||||
mac := req.Mac
|
||||
var danceList []model.Dance
|
||||
err = dao.DeviceDance.Ctx(ctx).Where(do.DeviceDance{
|
||||
Mac: mac,
|
||||
}).Scan(&danceList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Core modification: insert default data only when query result is empty
|
||||
if len(danceList) == 0 {
|
||||
// Insert single default dance data
|
||||
defaultDance := do.DeviceDance{
|
||||
DanceName: "StackChan Dance",
|
||||
Mac: mac,
|
||||
MusicUrl: "http://47.113.125.164:12800/file/music/stackchan_music.mp3",
|
||||
DanceData: model.DefaultDanceData,
|
||||
}
|
||||
_, err = dao.DeviceDance.Ctx(ctx).Data(defaultDance).Insert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Re-query list (one data exists now)
|
||||
err = dao.DeviceDance.Ctx(ctx).Where(do.DeviceDance{
|
||||
Mac: mac,
|
||||
}).Scan(&danceList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return new(v2.GetListRes(danceList)), nil
|
||||
}
|
||||
@@ -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/do"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"stackChan/api/dance/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) Update(ctx context.Context, req *v2.UpdateReq) (res *v2.UpdateRes, err error) {
|
||||
if req.Id == 0 { // Adjust based on actual type of req.Id (int/string)
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "Dance ID cannot be empty")
|
||||
}
|
||||
updateData := do.DeviceDance{}
|
||||
if req.MusicUrl != "" {
|
||||
updateData.MusicUrl = req.MusicUrl
|
||||
}
|
||||
if req.DanceName != "" {
|
||||
updateData.DanceName = req.DanceName
|
||||
}
|
||||
if req.DanceData != nil {
|
||||
danceJSON, err := json.Marshal(req.DanceData)
|
||||
if err != nil {
|
||||
// Wrap serialization error, add business prompt
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "Dance data serialization failed: %v")
|
||||
}
|
||||
updateData.DanceData = danceJSON
|
||||
}
|
||||
_, err = dao.DeviceDance.Ctx(ctx).Where("id=?", req.Id).Data(updateData).Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new(v2.UpdateRes("Update successful")), nil
|
||||
}
|
||||
@@ -18,3 +18,9 @@ type ControllerV1 struct{}
|
||||
func NewV1() device.IDeviceV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
|
||||
type ControllerV2 struct{}
|
||||
|
||||
func NewV2() device.IDeviceV2 {
|
||||
return &ControllerV2{}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,21 @@ import (
|
||||
"context"
|
||||
"stackChan/api/device/v1"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
insertId, err := dao.Device.Ctx(ctx).Data(do.Device{
|
||||
Mac: req.Mac,
|
||||
Mac: mac,
|
||||
Name: req.Name,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
|
||||
@@ -11,11 +11,19 @@ import (
|
||||
"stackChan/internal/model"
|
||||
|
||||
"stackChan/api/device/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetDeviceInfo(ctx context.Context, req *v1.GetDeviceInfoReq) (res *v1.GetDeviceInfoRes, err error) {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
var info model.DeviceInfo
|
||||
err = dao.Device.Ctx(ctx).WherePri(req.Mac).Scan(&info)
|
||||
err = dao.Device.Ctx(ctx).WherePri(mac).Scan(&info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,14 +9,28 @@ import (
|
||||
"context"
|
||||
"stackChan/api/device/v1"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/entity"
|
||||
"stackChan/internal/web_socket"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetRandomDevice(ctx context.Context, req *v1.GetRandomDeviceReq) (res *v1.GetRandomDeviceRes, err error) {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
|
||||
pageSize := req.PageSize
|
||||
if pageSize <= 0 {
|
||||
pageSize = 6
|
||||
}
|
||||
|
||||
// Obtain the list of online StackChan mac addresses (excluding the current user) from the websocket layer.
|
||||
macList := web_socket.GetRandomStackChanDevice(req.Mac, 6)
|
||||
macList := web_socket.GetRandomStackChanDevice(mac, pageSize)
|
||||
|
||||
if len(macList) == 0 {
|
||||
res = (*v1.GetRandomDeviceRes)(&[]entity.Device{})
|
||||
|
||||
@@ -8,14 +8,23 @@ package device
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"stackChan/api/device/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
_, err = dao.Device.Ctx(ctx).Data(do.Device{
|
||||
Name: req.Name,
|
||||
}).WherePri(req.Mac).Update()
|
||||
}).WherePri(mac).Update()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,17 +8,26 @@ package device
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"stackChan/api/device/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) UpdateDeviceInfo(ctx context.Context, req *v1.UpdateDeviceInfoReq) (res *v1.UpdateDeviceInfoRes, err error) {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
doDevice := do.Device{}
|
||||
if req.Name != "" {
|
||||
doDevice.Name = req.Name
|
||||
}
|
||||
_, err = dao.Device.Ctx(ctx).Data(doDevice).WherePri(req.Mac).Update()
|
||||
_, err = dao.Device.Ctx(ctx).Data(doDevice).WherePri(mac).Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/api/device/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) AgentRestoreDefault(ctx context.Context, req *v2.AgentRestoreDefaultReq) (res *v2.AgentRestoreDefaultRes, err error) {
|
||||
return new(v2.AgentRestoreDefaultRes(true)), err
|
||||
//if req.Mac == "" {
|
||||
// return nil, gerror.NewCode(gcode.CodeMissingParameter, "Device MAC address cannot be empty")
|
||||
//}
|
||||
//restoreResponse, err := service.RestoreDefaultAgent(req.Mac)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//if !restoreResponse {
|
||||
// return nil, gerror.NewCode(gcode.CodeInternalError, "Failed to restore default configuration")
|
||||
//}
|
||||
//return new(v2.AgentRestoreDefaultRes(true)), nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
"stackChan/api/device/v2"
|
||||
)
|
||||
|
||||
// BindDevice Device binding interface
|
||||
func (c *ControllerV2) BindDevice(ctx context.Context, req *v2.BindDeviceReq) (res *v2.BindDeviceRes, err error) {
|
||||
// 1. Get current logged-in user UID (from context)
|
||||
_, err = service.CreateMacIfNotExists(ctx, req.Mac)
|
||||
uid := g.RequestFromCtx(ctx).GetCtxVar(model.Uid).Int64()
|
||||
if uid == 0 {
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "User UID cannot be empty")
|
||||
}
|
||||
if req.Mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "Device MAC address cannot be empty")
|
||||
}
|
||||
_, err = dao.Device.Ctx(ctx).
|
||||
Where("mac = ?", req.Mac).
|
||||
Data("uid", uid, "bind_time", gtime.Now().Format("Y-m-d H:i:s")).
|
||||
Update()
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeDbOperationError, err, "Device binding failed")
|
||||
}
|
||||
return new(v2.BindDeviceRes(true)), nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/device/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) GetDevices(ctx context.Context, req *v2.GetDevicesReq) (res *v2.GetDevicesRes, err error) {
|
||||
uid := g.RequestFromCtx(ctx).GetCtxVar(model.Uid).Int64()
|
||||
if uid == 0 {
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "user UID is required")
|
||||
}
|
||||
devices := make([]model.DeviceInfo, 0)
|
||||
err = dao.Device.Ctx(ctx).Where("uid=?", uid).Scan(&devices)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new(v2.GetDevicesRes(devices)), nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/service"
|
||||
"stackChan/internal/xiaozhi"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/device/v2"
|
||||
)
|
||||
|
||||
// UnbindDevice Device unbinding interface
|
||||
func (c *ControllerV2) UnbindDevice(ctx context.Context, req *v2.UnbindDeviceReq) (res *v2.UnbindDeviceRes, err error) {
|
||||
_, err = service.CreateMacIfNotExists(ctx, req.Mac)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeDbOperationError, err, "Failed to initialize device information")
|
||||
}
|
||||
uid := g.RequestFromCtx(ctx).GetCtxVar(model.Uid).Int64()
|
||||
if uid == 0 {
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "User UID cannot be empty")
|
||||
}
|
||||
|
||||
// 3. Validate MAC address parameter
|
||||
if req.Mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "Device MAC address cannot be empty")
|
||||
}
|
||||
|
||||
restoreResponse, err := service.RestoreDefaultAgent(req.Mac)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !restoreResponse {
|
||||
return nil, gerror.NewCode(gcode.CodeInternalError, "Failed to restore default configuration")
|
||||
}
|
||||
|
||||
// xiaozhi Unbind Device
|
||||
unbindResponse, err := xiaozhi.UnbindDevice(&req.Mac)
|
||||
if err != nil {
|
||||
return nil, gerror.NewCode(gcode.CodeInternalError)
|
||||
}
|
||||
if !unbindResponse {
|
||||
g.Log().Error(ctx, "xiaozhi Unbind Device failed:")
|
||||
return nil, gerror.NewCode(gcode.CodeInternalError)
|
||||
}
|
||||
|
||||
// 4. Perform unbind: set uid to 0/NULL (only the current user's own device can be unbound)
|
||||
_, err = dao.Device.Ctx(ctx).
|
||||
Where("mac = ?", req.Mac).
|
||||
Where("uid = ?", uid).
|
||||
Data("uid", nil, "bind_time", nil).
|
||||
Update()
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeDbOperationError, err, "Device unbinding failed")
|
||||
}
|
||||
// 5. Return success response (consistent with bind interface format)
|
||||
return new(v2.UnbindDeviceRes(true)), nil
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/device/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) UpdateDevice(ctx context.Context, req *v2.UpdateDeviceReq) (res *v2.UpdateDeviceRes, err error) {
|
||||
// Get current logged-in user UID
|
||||
uid := g.RequestFromCtx(ctx).GetCtxVar(model.Uid).Int64()
|
||||
if uid == 0 {
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "User UID cannot be empty")
|
||||
}
|
||||
|
||||
// check device exists and belongs to current user
|
||||
count, err := dao.Device.Ctx(ctx).
|
||||
Where("mac = ?", req.Mac).
|
||||
Where("uid = ?", uid).
|
||||
Count()
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeDbOperationError, err, "Failed to query device information")
|
||||
}
|
||||
if count == 0 {
|
||||
return nil, gerror.NewCode(gcode.CodeNotFound, "device not found or not belong to current user")
|
||||
}
|
||||
|
||||
// build update data
|
||||
updateData := g.Map{}
|
||||
if req.Name != "" {
|
||||
updateData["name"] = req.Name
|
||||
}
|
||||
if req.Longitude != 0 {
|
||||
updateData["longitude"] = req.Longitude
|
||||
}
|
||||
if req.Latitude != 0 {
|
||||
updateData["latitude"] = req.Latitude
|
||||
}
|
||||
|
||||
// no need to update
|
||||
if len(updateData) == 0 {
|
||||
return new(v2.UpdateDeviceRes(true)), nil
|
||||
}
|
||||
|
||||
// update device information
|
||||
_, err = dao.Device.Ctx(ctx).
|
||||
Where("mac = ?", req.Mac).
|
||||
Where("uid = ?", uid).
|
||||
Data(updateData).
|
||||
Update()
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeDbOperationError, err, "update device failed")
|
||||
}
|
||||
|
||||
return new(v2.UpdateDeviceRes(true)), nil
|
||||
}
|
||||
@@ -8,18 +8,25 @@ package friend
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/friend/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Add(ctx context.Context, req *v1.AddReq) (res *v1.AddRes, err error) {
|
||||
if req.Mac == req.FriendMac {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
if mac == req.FriendMac {
|
||||
return nil, gerror.New("You cannot add yourself as a friend")
|
||||
}
|
||||
macA := req.Mac
|
||||
macA := mac
|
||||
macB := req.FriendMac
|
||||
var friend entity.DeviceFriend
|
||||
err = dao.DeviceFriend.Ctx(ctx).
|
||||
|
||||
@@ -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 pano
|
||||
@@ -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 pano
|
||||
|
||||
import (
|
||||
"stackChan/api/pano"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() pano.IPanoV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package pano
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/pano/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) AddPano(ctx context.Context, req *v1.AddPanoReq) (res *v1.AddPanoRes, err error) {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
|
||||
if req.Url == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
|
||||
Id, err := dao.DevicePano.Ctx(ctx).Data(entity.DevicePano{
|
||||
Mac: mac,
|
||||
PanoUrl: req.Url,
|
||||
}).InsertAndGetId()
|
||||
|
||||
if err != nil {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
|
||||
res = &v1.AddPanoRes{
|
||||
Id: Id,
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package pano
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/pano/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetPanoList(ctx context.Context, req *v1.GetPanoListReq) (res *v1.GetPanoListRes, err error) {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
|
||||
var list []model.Pano
|
||||
|
||||
err = dao.DevicePano.Ctx(ctx).Where("mac = ?", mac).Scan(&list)
|
||||
|
||||
if err != nil {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
|
||||
if list == nil {
|
||||
list = make([]model.Pano, 0)
|
||||
}
|
||||
|
||||
response := v1.GetPanoListRes(list)
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
@@ -8,16 +8,22 @@ package post
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"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()
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
device, err := dao.Device.Ctx(ctx).Where("mac", mac).One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -25,7 +31,7 @@ func (c *ControllerV1) CreatePost(ctx context.Context, req *v1.CreatePostReq) (r
|
||||
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,
|
||||
Mac: mac,
|
||||
ContentText: req.ContentText,
|
||||
ContentImage: req.ContentImage,
|
||||
}).InsertAndGetId()
|
||||
|
||||
@@ -8,15 +8,24 @@ package post
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/do"
|
||||
|
||||
"stackChan/api/post/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) CreatePostComment(ctx context.Context, req *v1.CreatePostCommentReq) (res *v1.CreatePostCommentRes, err error) {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
id, err := dao.DevicePostComment.Ctx(ctx).Data(do.DevicePostComment{
|
||||
PostId: req.PostId,
|
||||
Mac: req.Mac,
|
||||
Mac: mac,
|
||||
Content: req.Content,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
|
||||
@@ -12,11 +12,19 @@ import (
|
||||
"stackChan/internal/model"
|
||||
|
||||
"stackChan/api/post/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) DeletePostComment(ctx context.Context, req *v1.DeletePostCommentReq) (res *v1.DeletePostCommentRes, err error) {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
var postComment model.PostComment
|
||||
err = dao.DevicePostComment.Ctx(ctx).Where("id=?", req.Id).Scan(&postComment)
|
||||
err = dao.DevicePostComment.Ctx(ctx).Where("id=? AND post_id=? AND mac=?", req.CommentId, req.PostId, mac).Scan(&postComment)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -26,13 +34,13 @@ func (c *ControllerV1) DeletePostComment(ctx context.Context, req *v1.DeletePost
|
||||
return nil, errors.New("post not found")
|
||||
}
|
||||
|
||||
if postComment.Mac != req.Mac {
|
||||
if postComment.Mac != mac {
|
||||
return nil, errors.New("no authority to delete")
|
||||
}
|
||||
|
||||
_, err = dao.DevicePostComment.
|
||||
Ctx(ctx).
|
||||
Where("id = ? AND mac = ?", req.Id, req.Mac).
|
||||
Where("id=? AND post_id=?", req.CommentId, req.PostId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -26,7 +26,7 @@ func (c *ControllerV1) GetPostComment(ctx context.Context, req *v1.GetPostCommen
|
||||
|
||||
var list []*model.PostComment
|
||||
|
||||
db := dao.DevicePostComment.Ctx(ctx).As("dp").Where("mac = ? AND post_id = ?", req.Mac, req.PostId)
|
||||
db := dao.DevicePostComment.Ctx(ctx).As("dp").Where("post_id = ?", req.PostId)
|
||||
|
||||
total, err := db.Count()
|
||||
if err != 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 stackchandevice
|
||||
@@ -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 stackchandevice
|
||||
|
||||
import (
|
||||
"stackChan/api/stackchandevice"
|
||||
)
|
||||
|
||||
type ControllerV2 struct{}
|
||||
|
||||
func NewV2() stackchandevice.IStackchandeviceV2 {
|
||||
return &ControllerV2{}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package stackchandevice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/model/entity"
|
||||
"stackChan/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/stackchandevice/v2"
|
||||
)
|
||||
|
||||
// GetDeviceUserInfo Get user information corresponding to the device
|
||||
func (c *ControllerV2) GetDeviceUserInfo(ctx context.Context, req *v2.GetDeviceUserInfoReq) (res *v2.GetDeviceUserInfoRes, err error) {
|
||||
// 1. Get MAC address from context
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, "Device MAC address is empty")
|
||||
}
|
||||
|
||||
// 2. Ensure MAC record exists
|
||||
_, err = service.CreateMacIfNotExists(ctx, mac)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeInternalError, err, "create mac record failed")
|
||||
}
|
||||
|
||||
// 3. Query device information based on MAC
|
||||
var device entity.Device
|
||||
err = dao.Device.Ctx(ctx).Where("mac", mac).Scan(&device)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCodef(gcode.CodeInternalError, err, "Failed to query device information")
|
||||
}
|
||||
|
||||
// 4. Device not bound to user -> return null
|
||||
if device.Uid == 0 {
|
||||
return new(v2.GetDeviceUserInfoRes(nil)), nil
|
||||
}
|
||||
|
||||
// 5. Query user information based on UID
|
||||
var user model.User
|
||||
err = dao.User.Ctx(ctx).Where("uid", device.Uid).Scan(&user)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeInternalError, err, "query user info failed")
|
||||
}
|
||||
|
||||
// 6. User does not exist -> return null
|
||||
if user.Uid == 0 {
|
||||
return new(v2.GetDeviceUserInfoRes(nil)), nil
|
||||
}
|
||||
|
||||
// 7. Return username normally
|
||||
return new(v2.GetDeviceUserInfoRes(&user)), nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package stackchandevice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/service"
|
||||
"stackChan/internal/xiaozhi"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/stackchandevice/v2"
|
||||
)
|
||||
|
||||
// UnbindDevice Unbind device from StackChan side
|
||||
func (c *ControllerV2) UnbindDevice(ctx context.Context, req *v2.UnbindDeviceReq) (res *v2.UnbindDeviceRes, err error) {
|
||||
mac := g.RequestFromCtx(ctx).GetCtxVar(model.Mac).String()
|
||||
if mac == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter)
|
||||
}
|
||||
_, err = service.CreateMacIfNotExists(ctx, mac)
|
||||
if err != nil {
|
||||
return nil, gerror.NewCode(gcode.CodeInternalError)
|
||||
}
|
||||
restoreResponse, err := service.RestoreDefaultAgent(mac)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !restoreResponse {
|
||||
return nil, gerror.NewCode(gcode.CodeInternalError, "restore default agent failed")
|
||||
}
|
||||
|
||||
/// xiaozhi Unbind Device
|
||||
unbindResponse, err := xiaozhi.UnbindDevice(&mac)
|
||||
if err != nil {
|
||||
return nil, gerror.NewCode(gcode.CodeInternalError)
|
||||
}
|
||||
if !unbindResponse {
|
||||
g.Log().Error(ctx, "xiaozhi Unbind Device failed")
|
||||
return nil, gerror.NewCode(gcode.CodeInternalError)
|
||||
}
|
||||
|
||||
/// update device table
|
||||
_, err = dao.Device.Ctx(ctx).
|
||||
Where("mac", mac).
|
||||
Data("uid", nil, "bind_time", nil).
|
||||
Update()
|
||||
if err != nil {
|
||||
return nil, gerror.NewCodef(gcode.CodeInternalError, "device unbind failed: %v", err)
|
||||
}
|
||||
|
||||
return new(v2.UnbindDeviceRes(true)), 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 user
|
||||
@@ -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 user
|
||||
|
||||
import (
|
||||
"stackChan/api/user"
|
||||
)
|
||||
|
||||
type ControllerV2 struct{}
|
||||
|
||||
func NewV2() user.IUserV2 {
|
||||
return &ControllerV2{}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/dao"
|
||||
"stackChan/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/user/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) GetUserInfo(ctx context.Context, req *v2.GetUserInfoReq) (res *v2.GetUserInfoRes, err error) {
|
||||
uid := g.RequestFromCtx(ctx).GetCtxVar(model.Uid).Int64()
|
||||
if uid == 0 {
|
||||
return nil, gerror.NewCode(gcode.CodeMissingParameter, "user UID is required")
|
||||
}
|
||||
var userInfo model.User
|
||||
err = dao.User.Ctx(ctx).Where("uid=?", uid).Scan(&userInfo)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(gcode.CodeDbOperationError, err, "failed to query user information")
|
||||
}
|
||||
if userInfo.Uid == 0 {
|
||||
return nil, gerror.NewCode(gcode.CodeNotFound, "user does not exist")
|
||||
}
|
||||
return new(v2.GetUserInfoRes(userInfo)), nil
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/service"
|
||||
|
||||
"stackChan/api/user/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) Login(ctx context.Context, req *v2.LoginReq) (res *v2.LoginRes, err error) {
|
||||
return service.Login(ctx, req)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/service"
|
||||
|
||||
"stackChan/api/user/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) Registration(ctx context.Context, req *v2.RegistrationReq) (res *v2.RegistrationRes, err error) {
|
||||
return service.Registration(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 xiaozhi
|
||||
@@ -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 xiaozhi
|
||||
|
||||
import (
|
||||
"stackChan/api/xiaozhi"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() xiaozhi.IXiaozhiV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package xiaozhi
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"stackChan/api/xiaozhi/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetXiaoZhiGenerateLicenseToken(ctx context.Context, req *v1.GetXiaoZhiGenerateLicenseTokenReq) (res *v1.GetXiaoZhiGenerateLicenseTokenRes, err error) {
|
||||
generateLicenseToken := g.Cfg().MustGet(ctx, "xiaozhi.generate_license_token").String()
|
||||
if generateLicenseToken == "" {
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "generate_license_token is empty")
|
||||
}
|
||||
return new(v1.GetXiaoZhiGenerateLicenseTokenRes(generateLicenseToken)), nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package xiaozhi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/xiaozhi"
|
||||
|
||||
"stackChan/api/xiaozhi/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetXiaoZhiToken(ctx context.Context, req *v1.GetXiaoZhiTokenReq) (res *v1.GetXiaoZhiTokenRes, err error) {
|
||||
token, err := xiaozhi.GetToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new(v1.GetXiaoZhiTokenRes(token)), nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package xiaozhi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"stackChan/internal/xiaozhi"
|
||||
|
||||
"stackChan/api/xiaozhi/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) RefreshToken(ctx context.Context, req *v1.RefreshTokenReq) (res *v1.RefreshTokenRes, err error) {
|
||||
token, err := xiaozhi.GetNewToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new(v1.RefreshTokenRes(token)), 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"
|
||||
)
|
||||
|
||||
// appStoreDao is the data access object for the table app_store.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type appStoreDao struct {
|
||||
*internal.AppStoreDao
|
||||
}
|
||||
|
||||
var (
|
||||
// AppStore is a globally accessible object for table app_store operations.
|
||||
AppStore = appStoreDao{internal.NewAppStoreDao()}
|
||||
)
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
||||
// devicePanoDao is the data access object for the table device_pano.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type devicePanoDao struct {
|
||||
*internal.DevicePanoDao
|
||||
}
|
||||
|
||||
var (
|
||||
// DevicePano is a globally accessible object for table device_pano operations.
|
||||
DevicePano = devicePanoDao{internal.NewDevicePanoDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// ==========================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// AppStoreDao is the data access object for the table app_store.
|
||||
type AppStoreDao 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 AppStoreColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// AppStoreColumns defines and stores column names for the table app_store.
|
||||
type AppStoreColumns struct {
|
||||
Id string //
|
||||
AppName string // App name
|
||||
AppIconUrl string // App icon URL
|
||||
Description string // App description
|
||||
FirmwareUrl string // Firmware / installation package download URL
|
||||
CreateAt string // Creation time
|
||||
UpdateAt string // Update time
|
||||
IsDeleted string // Is deleted, 0 normal 1 deleted
|
||||
}
|
||||
|
||||
// appStoreColumns holds the columns for the table app_store.
|
||||
var appStoreColumns = AppStoreColumns{
|
||||
Id: "id",
|
||||
AppName: "app_name",
|
||||
AppIconUrl: "app_icon_url",
|
||||
Description: "description",
|
||||
FirmwareUrl: "firmware_url",
|
||||
CreateAt: "create_at",
|
||||
UpdateAt: "update_at",
|
||||
IsDeleted: "is_deleted",
|
||||
}
|
||||
|
||||
// NewAppStoreDao creates and returns a new DAO object for table data access.
|
||||
func NewAppStoreDao(handlers ...gdb.ModelHandler) *AppStoreDao {
|
||||
return &AppStoreDao{
|
||||
group: "default",
|
||||
table: "app_store",
|
||||
columns: appStoreColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *AppStoreDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *AppStoreDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *AppStoreDao) Columns() AppStoreColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *AppStoreDao) 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 *AppStoreDao) 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 *AppStoreDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
@@ -21,14 +26,18 @@ type DeviceDao struct {
|
||||
|
||||
// DeviceColumns defines and stores column names for the table device.
|
||||
type DeviceColumns struct {
|
||||
Mac string //
|
||||
Name string //
|
||||
Mac string //
|
||||
Name string //
|
||||
Uid string // Bound user UID
|
||||
BindTime string // Device binding time
|
||||
}
|
||||
|
||||
// deviceColumns holds the columns for the table device.
|
||||
var deviceColumns = DeviceColumns{
|
||||
Mac: "mac",
|
||||
Name: "name",
|
||||
Mac: "mac",
|
||||
Name: "name",
|
||||
Uid: "uid",
|
||||
BindTime: "bind_time",
|
||||
}
|
||||
|
||||
// NewDeviceDao creates and returns a new DAO object for table data access.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
@@ -21,22 +26,24 @@ type DeviceDanceDao struct {
|
||||
|
||||
// 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 //
|
||||
Id string //
|
||||
Mac string // Device MAC address
|
||||
DanceName string // Dance name
|
||||
DanceData string // MotionData
|
||||
MusicUrl string // Dance background music URL
|
||||
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",
|
||||
Id: "id",
|
||||
Mac: "mac",
|
||||
DanceName: "dance_name",
|
||||
DanceData: "dance_data",
|
||||
MusicUrl: "music_url",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewDeviceDanceDao creates and returns a new DAO object for table data access.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// ==========================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// DevicePanoDao is the data access object for the table device_pano.
|
||||
type DevicePanoDao 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 DevicePanoColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// DevicePanoColumns defines and stores column names for the table device_pano.
|
||||
type DevicePanoColumns struct {
|
||||
Id string //
|
||||
Mac string // Device MAC address
|
||||
PanoUrl string // Panorama URL
|
||||
CreatedAt string // Creation time
|
||||
UpdatedAt string //
|
||||
}
|
||||
|
||||
// devicePanoColumns holds the columns for the table device_pano.
|
||||
var devicePanoColumns = DevicePanoColumns{
|
||||
Id: "id",
|
||||
Mac: "mac",
|
||||
PanoUrl: "pano_url",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewDevicePanoDao creates and returns a new DAO object for table data access.
|
||||
func NewDevicePanoDao(handlers ...gdb.ModelHandler) *DevicePanoDao {
|
||||
return &DevicePanoDao{
|
||||
group: "default",
|
||||
table: "device_pano",
|
||||
columns: devicePanoColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *DevicePanoDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *DevicePanoDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *DevicePanoDao) Columns() DevicePanoColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *DevicePanoDao) 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 *DevicePanoDao) 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 *DevicePanoDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
@@ -22,10 +27,10 @@ type DevicePostDao struct {
|
||||
// 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 // 发帖时间
|
||||
Mac string // Post device MAC
|
||||
ContentText string //
|
||||
ContentImage string // Image URL
|
||||
CreatedAt string // Post time
|
||||
}
|
||||
|
||||
// devicePostColumns holds the columns for the table device_post.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
@@ -22,10 +27,10 @@ type DevicePostCommentDao struct {
|
||||
// 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 // 评论时间
|
||||
PostId string // Post ID
|
||||
Mac string // Comment device MAC
|
||||
Content string //
|
||||
CreatedAt string // Comment time
|
||||
}
|
||||
|
||||
// devicePostCommentColumns holds the columns for the table device_post_comment.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// ==========================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// UserDao is the data access object for the table user.
|
||||
type UserDao 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 UserColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// UserColumns defines and stores column names for the table user.
|
||||
type UserColumns struct {
|
||||
Uid string // User unique UID (remote platform primary key)
|
||||
Username string // Login username
|
||||
Userslug string // User alias
|
||||
DisplayName string // User display name
|
||||
IconText string // User icon text
|
||||
IconBgColor string // Icon background color
|
||||
EmailConfirmed string // Email verified, 0-no 1-yes
|
||||
JoinDate string // Registration timestamp (milliseconds)
|
||||
LastOnline string // Last online timestamp (milliseconds)
|
||||
UserStatus string // User online status
|
||||
CreateAt string // Local creation time
|
||||
UpdateAt string // Local update time
|
||||
IsDeleted string // Is deleted, 0-normal 1-deleted
|
||||
}
|
||||
|
||||
// userColumns holds the columns for the table user.
|
||||
var userColumns = UserColumns{
|
||||
Uid: "uid",
|
||||
Username: "username",
|
||||
Userslug: "userslug",
|
||||
DisplayName: "display_name",
|
||||
IconText: "icon_text",
|
||||
IconBgColor: "icon_bg_color",
|
||||
EmailConfirmed: "email_confirmed",
|
||||
JoinDate: "join_date",
|
||||
LastOnline: "last_online",
|
||||
UserStatus: "user_status",
|
||||
CreateAt: "create_at",
|
||||
UpdateAt: "update_at",
|
||||
IsDeleted: "is_deleted",
|
||||
}
|
||||
|
||||
// NewUserDao creates and returns a new DAO object for table data access.
|
||||
func NewUserDao(handlers ...gdb.ModelHandler) *UserDao {
|
||||
return &UserDao{
|
||||
group: "default",
|
||||
table: "user",
|
||||
columns: userColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *UserDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *UserDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *UserDao) Columns() UserColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *UserDao) 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 *UserDao) 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 *UserDao) 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"
|
||||
)
|
||||
|
||||
// userDao is the data access object for the table user.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type userDao struct {
|
||||
*internal.UserDao
|
||||
}
|
||||
|
||||
var (
|
||||
// User is a globally accessible object for table user operations.
|
||||
User = userDao{internal.NewUserDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"stackChan/internal/model"
|
||||
"stackChan/internal/service"
|
||||
"stackChan/internal/web_socket"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// TokenAuthMiddleware token
|
||||
func TokenAuthMiddleware(r *ghttp.Request) {
|
||||
mac, err := web_socket.GetMac(r)
|
||||
if err != nil {
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
if mac != "" {
|
||||
r.SetCtxVar(model.Mac, mac)
|
||||
}
|
||||
r.Middleware.Next()
|
||||
}
|
||||
|
||||
func V2TokenAuthMiddleware(r *ghttp.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, "/stackChan/v2/user/login") || strings.HasPrefix(r.URL.Path, "/stackChan/v2/user/registration") {
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
tokenString := r.Header.Get("token")
|
||||
if tokenString == "" {
|
||||
r.Response.WriteJsonExit(gerror.NewCode(gcode.CodeNotAuthorized, "The token cannot be empty."))
|
||||
}
|
||||
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
|
||||
tokenString = strings.TrimSpace(tokenString)
|
||||
if tokenString == "" {
|
||||
r.Response.WriteJsonExit(gerror.NewCode(gcode.CodeNotAuthorized, "Invalid token format"))
|
||||
}
|
||||
jwtSecret := service.GetJwtSecret()
|
||||
if jwtSecret == "" {
|
||||
r.Response.WriteJsonExit(gerror.NewCode(gcode.CodeInternalError, "jwt The secret has not been configured."))
|
||||
}
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, gerror.NewCodef(gcode.CodeNotAuthorized, "token signing algorithm error: %v, %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(jwtSecret), nil
|
||||
})
|
||||
if err != nil || !token.Valid {
|
||||
r.Response.WriteJsonExit(gerror.NewCode(gcode.CodeNotAuthorized, "The token is invalid or has expired."))
|
||||
}
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
r.Response.WriteJsonExit(gerror.NewCode(gcode.CodeNotAuthorized, "Token payload format is incorrect"))
|
||||
}
|
||||
uid, ok := claims["id"].(float64)
|
||||
if !ok {
|
||||
r.Response.WriteJsonExit(gerror.NewCode(gcode.CodeNotAuthorized, "The user ID in the token is invalid."))
|
||||
}
|
||||
r.SetCtxVar(model.Uid, int64(uid))
|
||||
r.Middleware.Next()
|
||||
}
|
||||
|
||||
// CORS allow cross-origin
|
||||
func CORS(r *ghttp.Request) {
|
||||
r.Response.CORSDefault()
|
||||
r.Middleware.Next()
|
||||
}
|
||||
|
||||
// AdminTokenAuthMiddleware Admin token validation
|
||||
func AdminTokenAuthMiddleware(r *ghttp.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, "/admin/stackChan/login") {
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := r.Header.Get("Authorization")
|
||||
if tokenString == "" {
|
||||
r.Response.WriteJsonExit(gerror.NewCode(gcode.CodeNotAuthorized, "Token missing"))
|
||||
return
|
||||
}
|
||||
|
||||
jwtSecret := service.GetJwtSecret()
|
||||
if jwtSecret == "" {
|
||||
r.Response.WriteJsonExit(gerror.NewCode(gcode.CodeInternalError, "JWT secret has not been configured."))
|
||||
return
|
||||
}
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(jwtSecret), nil
|
||||
})
|
||||
if err != nil || !token.Valid {
|
||||
r.Response.WriteJsonExit(gerror.NewCode(gcode.CodeNotAuthorized, "The token is invalid."))
|
||||
return
|
||||
}
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
if Username, ok := claims[model.Username].(string); ok {
|
||||
if Username != "" {
|
||||
r.SetCtxVar(model.Username, Username)
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
r.Response.WriteJsonExit(gerror.NewCode(gcode.CodeNotAuthorized, "The username is missing in the token."))
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import "github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
type AppInfo struct {
|
||||
Id int64 `json:"id" orm:"id" description:""` // App ID
|
||||
AppName string `json:"appName" orm:"app_name" description:"App name"` // App name
|
||||
AppIconUrl string `json:"appIconUrl" orm:"app_icon_url" description:"App icon URL"` // App icon URL
|
||||
Description string `json:"description" orm:"description" description:"App description"` // App description
|
||||
FirmwareUrl string `json:"firmwareUrl" orm:"firmware_url" description:"Firmware / installation package download URL"` // Firmware / installation package download URL
|
||||
CreateAt *gtime.Time `json:"createAt" orm:"create_at" description:"Creation time"` // Creation time
|
||||
UpdateAt *gtime.Time `json:"updateAt" orm:"update_at" description:"Update time"` // Update time
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type Dance struct {
|
||||
Id int64 `json:"id" orm:"id" description:"Dance ID"` //
|
||||
DanceName string `json:"danceName" orm:"dance_name" description:"Dance name"` // Dance name
|
||||
MusicUrl string `json:"musicUrl" orm:"music_url" description:"Dance background music URL"` // Dance background music URL
|
||||
DanceData json.RawMessage `json:"danceData" orm:"dance_data" description:"MotionData"` // Dance motion data
|
||||
}
|
||||
@@ -6,6 +6,25 @@ 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"`
|
||||
Mac string `json:"mac" v:"required" description:"Mac address"`
|
||||
Name string `json:"name" v:"required" description:"Name"`
|
||||
Uid int64 `json:"uid" orm:"uid" description:"Bound user UID"` // Bound user UID
|
||||
BindTime string `json:"bind_time" orm:"bind_time" description:"Device binding time"` // Device binding time
|
||||
}
|
||||
|
||||
type IPLocation struct {
|
||||
Status string `json:"status"`
|
||||
Country string `json:"country"`
|
||||
CountryCode string `json:"countryCode"`
|
||||
Region string `json:"region"`
|
||||
RegionName string `json:"regionName"`
|
||||
City string `json:"city"`
|
||||
Zip string `json:"zip"`
|
||||
Lat float64 `json:"lat"`
|
||||
Lon float64 `json:"lon"`
|
||||
TimeZone string `json:"timezone"`
|
||||
Isp string `json:"isp"`
|
||||
Org string `json:"org"`
|
||||
As string `json:"as"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// AppStore is the golang structure of table app_store for DAO operations like Where/Data.
|
||||
type AppStore struct {
|
||||
g.Meta `orm:"table:app_store, do:true"`
|
||||
Id any //
|
||||
AppName any // App name
|
||||
AppIconUrl any // App icon URL
|
||||
Description any // App description
|
||||
FirmwareUrl any // Firmware / installation package download URL
|
||||
CreateAt *gtime.Time // Creation time
|
||||
UpdateAt *gtime.Time // Update time
|
||||
IsDeleted any // Is deleted, 0 normal 1 deleted
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
@@ -10,7 +15,9 @@ import (
|
||||
|
||||
// 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 //
|
||||
g.Meta `orm:"table:device, do:true"`
|
||||
Mac any //
|
||||
Name any //
|
||||
Uid any // Bound user UID
|
||||
BindTime any // Device binding time
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
@@ -11,11 +16,12 @@ import (
|
||||
|
||||
// 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 //
|
||||
g.Meta `orm:"table:device_dance, do:true"`
|
||||
Id any //
|
||||
Mac any // Device MAC address
|
||||
DanceName any // Dance name
|
||||
DanceData any // MotionData
|
||||
MusicUrl any // Dance background music URL
|
||||
CreatedAt *gtime.Time //
|
||||
UpdatedAt *gtime.Time //
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// DevicePano is the golang structure of table device_pano for DAO operations like Where/Data.
|
||||
type DevicePano struct {
|
||||
g.Meta `orm:"table:device_pano, do:true"`
|
||||
Id any //
|
||||
Mac any // Device MAC address
|
||||
PanoUrl any // Panorama URL
|
||||
CreatedAt *gtime.Time // Creation time
|
||||
UpdatedAt *gtime.Time //
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
@@ -13,8 +18,8 @@ import (
|
||||
type DevicePost struct {
|
||||
g.Meta `orm:"table:device_post, do:true"`
|
||||
Id any //
|
||||
Mac any // 发帖设备MAC
|
||||
ContentText any // 文本内容
|
||||
ContentImage any // 图片URL
|
||||
CreatedAt *gtime.Time // 发帖时间
|
||||
Mac any // Post device MAC
|
||||
ContentText any //
|
||||
ContentImage any // Image URL
|
||||
CreatedAt *gtime.Time // Post time
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
@@ -13,8 +18,8 @@ import (
|
||||
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 // 评论时间
|
||||
PostId any // Post ID
|
||||
Mac any // Comment device MAC
|
||||
Content any //
|
||||
CreatedAt *gtime.Time // Comment time
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// =================================================================================
|
||||
// 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,33 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// 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"
|
||||
)
|
||||
|
||||
// User is the golang structure of table user for DAO operations like Where/Data.
|
||||
type User struct {
|
||||
g.Meta `orm:"table:user, do:true"`
|
||||
Uid any // User unique UID (remote platform primary key)
|
||||
Username any // Login username
|
||||
Userslug any // User alias
|
||||
DisplayName any // User display name
|
||||
IconText any // User icon text
|
||||
IconBgColor any // Icon background color
|
||||
EmailConfirmed any // Email verified, 0-no 1-yes
|
||||
JoinDate any // Registration timestamp (milliseconds)
|
||||
LastOnline any // Last online timestamp (milliseconds)
|
||||
UserStatus any // User online status
|
||||
CreateAt *gtime.Time // Local creation time
|
||||
UpdateAt *gtime.Time // Local update time
|
||||
IsDeleted any // Is deleted, 0-normal 1-deleted
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
type Empty struct {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// AppStore is the golang structure for table app_store.
|
||||
type AppStore struct {
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
AppName string `json:"appName" orm:"app_name" description:"App name"` // App name
|
||||
AppIconUrl string `json:"appIconUrl" orm:"app_icon_url" description:"App icon URL"` // App icon URL
|
||||
Description string `json:"description" orm:"description" description:"App description"` // App description
|
||||
FirmwareUrl string `json:"firmwareUrl" orm:"firmware_url" description:"Firmware / installation package download URL"` // Firmware / installation package download URL
|
||||
CreateAt *gtime.Time `json:"createAt" orm:"create_at" description:"Creation time"` // Creation time
|
||||
UpdateAt *gtime.Time `json:"updateAt" orm:"update_at" description:"Update time"` // Update time
|
||||
IsDeleted int `json:"isDeleted" orm:"is_deleted" description:"Is deleted, 0 normal 1 deleted"` // Is deleted, 0 normal 1 deleted
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
@@ -6,6 +11,8 @@ 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:""` //
|
||||
Mac string `json:"mac" orm:"mac" description:""` //
|
||||
Name string `json:"name" orm:"name" description:""` //
|
||||
Uid int64 `json:"uid" orm:"uid" description:"Bound user UID"` // Bound user UID
|
||||
BindTime string `json:"bindTime" orm:"bind_time" description:"Device binding time"` // Device binding time
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
@@ -10,10 +15,11 @@ import (
|
||||
|
||||
// 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:""` //
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
Mac string `json:"mac" orm:"mac" description:"Device MAC address"` // Device MAC address
|
||||
DanceName string `json:"danceName" orm:"dance_name" description:"Dance name"` // Dance name
|
||||
DanceData string `json:"danceData" orm:"dance_data" description:"MotionData"` // MotionData
|
||||
MusicUrl string `json:"musicUrl" orm:"music_url" description:"Dance background music URL"` // Dance background music URL
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:""` //
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:""` //
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// DevicePano is the golang structure for table device_pano.
|
||||
type DevicePano struct {
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
Mac string `json:"mac" orm:"mac" description:"Device MAC address"` // Device MAC address
|
||||
PanoUrl string `json:"panoUrl" orm:"pano_url" description:"Panorama URL"` // Panorama URL
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"Creation time"` // Creation time
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:""` //
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
@@ -10,9 +15,9 @@ import (
|
||||
|
||||
// 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:"发帖时间"` // 发帖时间
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
Mac string `json:"mac" orm:"mac" description:"Post device MAC"` // Post device MAC
|
||||
ContentText string `json:"contentText" orm:"content_text" description:""` //
|
||||
ContentImage string `json:"contentImage" orm:"content_image" description:"Image URL"` // Image URL
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"Post time"` // Post time
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
@@ -10,9 +15,9 @@ import (
|
||||
|
||||
// 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:"评论时间"` // 评论时间
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
PostId int64 `json:"postId" orm:"post_id" description:"Post ID"` // Post ID
|
||||
Mac string `json:"mac" orm:"mac" description:"Comment device MAC"` // Comment device MAC
|
||||
Content string `json:"content" orm:"content" description:""` //
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"Comment time"` // Comment time
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// =================================================================================
|
||||
// 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,31 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// User is the golang structure for table user.
|
||||
type User struct {
|
||||
Uid int64 `json:"uid" orm:"uid" description:"User unique UID (remote platform primary key)"` // User unique UID (remote platform primary key)
|
||||
Username string `json:"username" orm:"username" description:"Login username"` // Login username
|
||||
Userslug string `json:"userslug" orm:"userslug" description:"User alias"` // User alias
|
||||
DisplayName string `json:"displayName" orm:"display_name" description:"User display name"` // User display name
|
||||
IconText string `json:"iconText" orm:"icon_text" description:"User icon text"` // User icon text
|
||||
IconBgColor string `json:"iconBgColor" orm:"icon_bg_color" description:"Icon background color"` // Icon background color
|
||||
EmailConfirmed int `json:"emailConfirmed" orm:"email_confirmed" description:"Email verified, 0-no 1-yes"` // Email verified, 0-no 1-yes
|
||||
JoinDate int64 `json:"joinDate" orm:"join_date" description:"Registration timestamp (milliseconds)"` // Registration timestamp (milliseconds)
|
||||
LastOnline int64 `json:"lastOnline" orm:"last_online" description:"Last online timestamp (milliseconds)"` // Last online timestamp (milliseconds)
|
||||
UserStatus string `json:"userStatus" orm:"user_status" description:"User online status"` // User online status
|
||||
CreateAt *gtime.Time `json:"createAt" orm:"create_at" description:"Local creation time"` // Local creation time
|
||||
UpdateAt *gtime.Time `json:"updateAt" orm:"update_at" description:"Local update time"` // Local update time
|
||||
IsDeleted int `json:"isDeleted" orm:"is_deleted" description:"Is deleted, 0-normal 1-deleted"` // Is deleted, 0-normal 1-deleted
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import "github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
type Pano struct {
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
PanoUrl string `json:"panoUrl" orm:"pano_url" description:"Panorama URL"` // Panorama URL
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"Creation time"` // Creation time
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:""` //
|
||||
}
|
||||
@@ -8,13 +8,13 @@ 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:"评论"`
|
||||
Id int64 `json:"id" orm:"id" description:"Post ID"`
|
||||
Mac string `json:"mac" orm:"mac" description:"Post device MAC"`
|
||||
Name string `json:"name" orm:"name" description:"Post device name"`
|
||||
ContentText string `json:"contentText" orm:"content_text" description:"Text content"`
|
||||
ContentImage string `json:"contentImage" orm:"content_image" description:"Image URL"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"Post time"`
|
||||
PostCommentList []*PostComment `json:"postCommentList" orm:"postCommentList" description:"Comments"`
|
||||
}
|
||||
|
||||
type PostComment struct {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
type User struct {
|
||||
Uid int64 `json:"uid" orm:"uid" description:"User unique UID (remote platform primary key)"` // User unique UID (remote platform primary key)
|
||||
Username string `json:"username" orm:"username" description:"Login username"` // Login username
|
||||
Userslug string `json:"userslug" orm:"userslug" description:"User alias"` // User alias
|
||||
DisplayName string `json:"displayName" orm:"display_name" description:"User display name"` // User display name
|
||||
IconText string `json:"iconText" orm:"icon_text" description:"User icon text"` // User icon text
|
||||
IconBgColor string `json:"iconBgColor" orm:"icon_bg_color" description:"Icon background color"` // Icon background color
|
||||
EmailConfirmed int `json:"emailConfirmed" orm:"email_confirmed" description:"Email verified, 0-no 1-yes"` // Email verified, 0-no 1-yes
|
||||
JoinDate int64 `json:"joinDate" orm:"join_date" description:"Registration timestamp (milliseconds)"` // Registration timestamp (milliseconds)
|
||||
LastOnline int64 `json:"lastOnline" orm:"last_online" description:"Last online timestamp (milliseconds)"` // Last online timestamp (milliseconds)
|
||||
UserStatus string `json:"userStatus" orm:"user_status" description:"User online status"` // User online status
|
||||
}
|
||||
|
||||
type RemoteLoginResp struct {
|
||||
Status struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"status"`
|
||||
Response struct {
|
||||
Uid int64 `json:"uid"`
|
||||
Username string `json:"username"`
|
||||
Userslug string `json:"userslug"`
|
||||
Picture interface{} `json:"picture"`
|
||||
Status string `json:"status"`
|
||||
Postcount int `json:"postcount"`
|
||||
Reputation int `json:"reputation"`
|
||||
EmailConfirmed int `json:"email:confirmed"`
|
||||
Lastonline int64 `json:"lastonline"`
|
||||
Flags interface{} `json:"flags"`
|
||||
Banned bool `json:"banned"`
|
||||
BannedExpire int `json:"banned:expire"`
|
||||
Joindate int64 `json:"joindate"`
|
||||
Fullname interface{} `json:"fullname"`
|
||||
Displayname string `json:"displayname"`
|
||||
IconText string `json:"icon:text"`
|
||||
IconBgColor string `json:"icon:bgColor"`
|
||||
JoindateISO string `json:"joindateISO"`
|
||||
LastonlineISO string `json:"lastonlineISO"`
|
||||
BannedUntil int `json:"banned_until"`
|
||||
BannedReadable string `json:"banned_until_readable"`
|
||||
} `json:"response"`
|
||||
}
|
||||
|
||||
type RegistrationResponse struct {
|
||||
Uid int64 `json:"uid"`
|
||||
Username string `json:"username"`
|
||||
Userslug string `json:"userslug"`
|
||||
Email string `json:"email"`
|
||||
EmailConfirmed int `json:"email:confirmed"`
|
||||
JoinDate int64 `json:"joindate"`
|
||||
LastOnline int64 `json:"lastonline"`
|
||||
Picture interface{} `json:"picture"`
|
||||
IconBgColor string `json:"icon:bgColor"`
|
||||
Fullname interface{} `json:"fullname"`
|
||||
Displayname string `json:"displayname"`
|
||||
IconText string `json:"icon:text"`
|
||||
UserStatus string `json:"status"` // User status: online
|
||||
}
|
||||
|
||||
type RemoteRegisterResp struct {
|
||||
Status struct {
|
||||
Code string `json:"code"` // Status code: ok/bad-request
|
||||
Message string `json:"message"` // Error message / success message
|
||||
} `json:"status"`
|
||||
RegistrationResponse `json:"response"`
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type WsSendMsg struct {
|
||||
MsgType int
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type AppClient struct {
|
||||
mac string
|
||||
conn *websocket.Conn
|
||||
mu sync.RWMutex
|
||||
deviceId string
|
||||
lastTime time.Time
|
||||
|
||||
sendChan chan *WsSendMsg
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
type StackChanClient struct {
|
||||
mac string
|
||||
conn *websocket.Conn
|
||||
mu sync.RWMutex
|
||||
cameraSubscriptionList []*AppClient
|
||||
audioSubscriptionList []*AppClient
|
||||
callAppClient *AppClient
|
||||
aimedTakePhotoAppClient *AppClient
|
||||
phoneScreen bool
|
||||
lastTime time.Time
|
||||
|
||||
sendChan chan *WsSendMsg
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// NewAppClient creates and initializes an AppClient
|
||||
func NewAppClient(mac string, conn *websocket.Conn, deviceId string) *AppClient {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
client := &AppClient{
|
||||
mac: mac,
|
||||
conn: conn,
|
||||
deviceId: deviceId,
|
||||
lastTime: time.Now(),
|
||||
sendChan: make(chan *WsSendMsg, 100),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
client.StartWriterCoroutine()
|
||||
return client
|
||||
}
|
||||
|
||||
// NewStackChanClient creates and initializes a StackChanClient
|
||||
func NewStackChanClient(mac string, conn *websocket.Conn, cameraSubscriptionList []*AppClient, callAppClient *AppClient, phoneScreen bool) *StackChanClient {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
client := &StackChanClient{
|
||||
mac: mac,
|
||||
conn: conn,
|
||||
cameraSubscriptionList: cameraSubscriptionList,
|
||||
callAppClient: callAppClient,
|
||||
phoneScreen: phoneScreen,
|
||||
lastTime: time.Now(),
|
||||
sendChan: make(chan *WsSendMsg, 100),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
client.StartWriterCoroutine()
|
||||
return client
|
||||
}
|
||||
|
||||
// StartWriterCoroutine AppClient Start message sending coroutine
|
||||
func (a *AppClient) StartWriterCoroutine() {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
g.Log().Errorf(context.Background(), "AppClient writer coroutine panic: %v", r)
|
||||
}
|
||||
close(a.sendChan)
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
case msg, ok := <-a.sendChan:
|
||||
if !ok { // Channel closed
|
||||
return
|
||||
}
|
||||
if msg == nil {
|
||||
continue
|
||||
}
|
||||
a.mu.RLock()
|
||||
conn := a.conn
|
||||
a.mu.RUnlock()
|
||||
if conn == nil {
|
||||
continue
|
||||
}
|
||||
if err := conn.WriteMessage(msg.MsgType, msg.Data); err != nil {
|
||||
g.Log().Errorf(context.Background(), "AppClient send message error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// StartWriterCoroutine StackChanClient Start message sending coroutine
|
||||
func (s *StackChanClient) StartWriterCoroutine() {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
g.Log().Errorf(context.Background(), "StackChan writer coroutine panic: %v", r)
|
||||
}
|
||||
close(s.sendChan)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
case msg, ok := <-s.sendChan:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if msg == nil {
|
||||
continue
|
||||
}
|
||||
s.mu.RLock()
|
||||
conn := s.conn
|
||||
s.mu.RUnlock()
|
||||
if conn == nil {
|
||||
continue
|
||||
}
|
||||
if err := conn.WriteMessage(msg.MsgType, msg.Data); err != nil {
|
||||
g.Log().Errorf(context.Background(), "StackChan writer coroutine send message error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *AppClient) CloseWriterCoroutine() {
|
||||
a.cancel()
|
||||
}
|
||||
|
||||
func (s *StackChanClient) CloseWriterCoroutine() {
|
||||
s.cancel()
|
||||
}
|
||||
|
||||
func (a *AppClient) SendChan() chan *WsSendMsg {
|
||||
return a.sendChan
|
||||
}
|
||||
|
||||
func (s *StackChanClient) SendChan() chan *WsSendMsg {
|
||||
return s.sendChan
|
||||
}
|
||||
|
||||
func (a *AppClient) SetMac(mac string) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.mac = mac
|
||||
}
|
||||
|
||||
func (a *AppClient) GetMac() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.mac
|
||||
}
|
||||
|
||||
func (a *AppClient) GetConn() *websocket.Conn {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.conn
|
||||
}
|
||||
|
||||
func (a *AppClient) SetConn(conn *websocket.Conn) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.conn = conn
|
||||
}
|
||||
|
||||
func (a *AppClient) SetDeviceId(deviceId string) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.deviceId = deviceId
|
||||
}
|
||||
|
||||
func (a *AppClient) GetDeviceId() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.deviceId
|
||||
}
|
||||
|
||||
func (a *AppClient) SetLastTime(lastTime time.Time) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.lastTime = lastTime
|
||||
}
|
||||
|
||||
func (a *AppClient) GetLastTime() time.Time {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.lastTime
|
||||
}
|
||||
|
||||
func (s *StackChanClient) SetMac(mac string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.mac = mac
|
||||
}
|
||||
|
||||
func (s *StackChanClient) GetMac() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.mac
|
||||
}
|
||||
|
||||
func (s *StackChanClient) GetConn() *websocket.Conn {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.conn
|
||||
}
|
||||
|
||||
func (s *StackChanClient) SetConn(conn *websocket.Conn) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.conn = conn
|
||||
}
|
||||
|
||||
func (s *StackChanClient) SetCameraSubscriptionList(cameraSubscriptionList []*AppClient) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.cameraSubscriptionList = cameraSubscriptionList
|
||||
}
|
||||
|
||||
func (s *StackChanClient) AppendCameraSubscriptionList(appClient *AppClient) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.cameraSubscriptionList = append(s.cameraSubscriptionList, appClient)
|
||||
}
|
||||
|
||||
func (s *StackChanClient) GetCameraSubscriptionList() []*AppClient {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
out := make([]*AppClient, len(s.cameraSubscriptionList))
|
||||
copy(out, s.cameraSubscriptionList)
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *StackChanClient) SetAudioSubscriptionList(audioSubscriptionList []*AppClient) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.audioSubscriptionList = audioSubscriptionList
|
||||
}
|
||||
|
||||
func (s *StackChanClient) GetAudioSubscriptionList() []*AppClient {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
out := make([]*AppClient, len(s.audioSubscriptionList))
|
||||
copy(out, s.audioSubscriptionList)
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *StackChanClient) SetCallAppClient(client *AppClient) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.callAppClient = client
|
||||
}
|
||||
|
||||
func (s *StackChanClient) GetCallAppClient() *AppClient {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.callAppClient
|
||||
}
|
||||
|
||||
func (s *StackChanClient) GetAimedTakePhotoAppClient() *AppClient {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.aimedTakePhotoAppClient
|
||||
}
|
||||
|
||||
func (s *StackChanClient) SetAimedTakePhotoAppClient(aimedTakePhotoAppClient *AppClient) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.aimedTakePhotoAppClient = aimedTakePhotoAppClient
|
||||
}
|
||||
|
||||
func (s *StackChanClient) GetPhoneScreen() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.phoneScreen
|
||||
}
|
||||
|
||||
func (s *StackChanClient) SetPhoneScreen(phoneScreen bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.phoneScreen = phoneScreen
|
||||
}
|
||||
|
||||
func (s *StackChanClient) GetLastTime() time.Time {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.lastTime
|
||||
}
|
||||
|
||||
func (s *StackChanClient) SetLastTime(lastTime time.Time) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.lastTime = lastTime
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package xiaozhi
|
||||
|
||||
import "time"
|
||||
|
||||
type XiaoZhiResponse[T any] struct {
|
||||
Success bool `json:"success"`
|
||||
Data *T `json:"data"`
|
||||
Message string `json:"message"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
Token string `json:"token"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
type Pagination struct {
|
||||
Total int `json:"total"`
|
||||
Current int `json:"current"`
|
||||
PageSize int `json:"pageSize"`
|
||||
HasMore bool `json:"hasMore"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotaPages int `json:"totaPages"`
|
||||
}
|
||||
|
||||
type ListData[T any] struct {
|
||||
List []T `json:"list"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type AgentTemplate struct {
|
||||
Id int `json:"id"`
|
||||
DeveloperId int `json:"developer_id"`
|
||||
AgentName string `json:"agent_name"`
|
||||
TtsVoices []string `json:"tts_voices"`
|
||||
DefaultTtsVoice string `json:"default_tts_voice"`
|
||||
LlmModel string `json:"llm_model"`
|
||||
AssistantName string `json:"assistant_name"`
|
||||
UserName string `json:"user_name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Character string `json:"character"`
|
||||
TtsSpeechSpeed string `json:"tts_speech_speed"`
|
||||
AsrSpeed string `json:"asr_speed"`
|
||||
TtsPitch int `json:"tts_pitch"`
|
||||
KnowledgeBaseIds []int `json:"knowledge_base_ids"`
|
||||
XiaoZhiVersion string `json:"xiao_zhi_version"`
|
||||
TtsVoiceName string `json:"tts_voice_name"`
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
AgentName string `json:"agent_name"`
|
||||
AssistantName string `json:"assistant_name"`
|
||||
LlmModel string `json:"llm_model"`
|
||||
TtsVoice string `json:"tts_voice"`
|
||||
TtsSpeechSpeed string `json:"tts_speech_speed"`
|
||||
TtsPitch int `json:"tts_pitch"`
|
||||
AsrSpeed string `json:"asr_speed"`
|
||||
Language string `json:"language"`
|
||||
Character string `json:"character"`
|
||||
Memory string `json:"memory"`
|
||||
MemoryType string `json:"memory_type"`
|
||||
KnowledgeBaseIds []int `json:"knowledge_base_ids"`
|
||||
McpEndpoints []string `json:"mcp_endpoints"`
|
||||
ProductMcpEndpoints []string `json:"product_mcp_endpoints"`
|
||||
}
|
||||
|
||||
type CreateAgentResponse struct {
|
||||
Id int `json:"id"`
|
||||
}
|
||||
|
||||
// Agent class
|
||||
type Agent struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
AgentName string `json:"agent_name"`
|
||||
TtsVoice string `json:"tts_voice"`
|
||||
LlmModel string `json:"llm_model"`
|
||||
AssistantName string `json:"assistant_name"`
|
||||
UserName string `json:"user_name"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Memory string `json:"memory"`
|
||||
Character string `json:"character"`
|
||||
LongMemorySwitch int `json:"long_memory_switch"`
|
||||
LangCode string `json:"lang_code"`
|
||||
Language string `json:"language"`
|
||||
TtsSpeechSpeed string `json:"tts_speech_speed"`
|
||||
AsrSpeed string `json:"asr_speed"`
|
||||
TtsPitch int `json:"tts_pitch"`
|
||||
AgentTemplateID int64 `json:"agent_template_id"`
|
||||
MemoryUpdatedAt time.Time `json:"memory_updated_at"`
|
||||
ShareAgentID *int64 `json:"share_agent_id"`
|
||||
Source string `json:"source"`
|
||||
McpEndpoints []string `json:"mcp_endpoints"`
|
||||
MemoryType string `json:"memory_type"`
|
||||
KnowledgeBaseIDs []int64 `json:"knowledge_base_ids"`
|
||||
MaxMessageCount *int64 `json:"max_message_count"`
|
||||
ProductMcpEndpoints []string `json:"product_mcp_endpoints"`
|
||||
DeviceCount int `json:"deviceCount"`
|
||||
LastDevice LastDevice `json:"lastDevice"`
|
||||
}
|
||||
|
||||
// LastDevice nested device struct
|
||||
type LastDevice struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
MacAddress string `json:"mac_address"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LastConnectedAt time.Time `json:"last_connected_at"`
|
||||
AutoUpdate int `json:"auto_update"`
|
||||
Alias *string `json:"alias"`
|
||||
AgentID int64 `json:"agent_id"`
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package xiaozhi
|
||||
|
||||
type Conversation struct {
|
||||
Id int `json:"id"`
|
||||
UserId int `json:"user_id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
DeviceId int `json:"device_id"`
|
||||
MsgCount int `json:"msg_count"`
|
||||
AgentId int `json:"agent_id"`
|
||||
Model string `json:"model"`
|
||||
TokenCount int `json:"token_count"`
|
||||
Duration int `json:"duration"`
|
||||
ChatSummary ChatSummary `json:"chat_summary"`
|
||||
}
|
||||
|
||||
type ChatSummary struct {
|
||||
Title string `json:"title"`
|
||||
Summary string `json:"summary"`
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user