server code 4/27

This commit is contained in:
袁智鸿
2026-04-27 10:35:03 +08:00
parent f0fa33cd67
commit 2ccbcd01c0
143 changed files with 5812 additions and 756 deletions
+95
View File
@@ -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
View File
@@ -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).
+10
View File
@@ -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
}
+10
View File
@@ -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
}
+27
View File
@@ -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.
+27
View File
@@ -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.
+98
View File
@@ -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)
}
+13 -4
View File
@@ -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.
+19 -12
View File
@@ -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)
}
+9 -4
View File
@@ -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.
// ==========================================================================
+108
View File
@@ -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)
}
+27
View File
@@ -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.
+113
View File
@@ -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."))
}
+18
View File
@@ -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
}
+15
View File
@@ -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
}
+21 -2
View File
@@ -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"`
}
+28
View File
@@ -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
}
+10 -3
View File
@@ -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
}
+13 -7
View File
@@ -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.
// =================================================================================
+25
View File
@@ -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 //
}
+9 -4
View File
@@ -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 //
}
+33
View File
@@ -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
}
+9
View File
@@ -0,0 +1,9 @@
/*
SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
SPDX-License-Identifier: MIT
*/
package model
type Empty struct {
}
+26
View File
@@ -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
}
+9 -2
View File
@@ -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
}
+12 -6
View File
@@ -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:""` //
}
+10 -5
View File
@@ -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:""` //
}
+31
View File
@@ -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
}
+15
View File
@@ -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:""` //
}
+7 -7
View File
@@ -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 {
+73
View File
@@ -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
+321
View File
@@ -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
}
+118
View File
@@ -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