排班管理、主播管理、直播账号管理

This commit is contained in:
2026-04-17 16:28:31 +08:00
commit adb6da1d70
24 changed files with 1861 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/.idea/*

49
Dockerfile Normal file
View File

@@ -0,0 +1,49 @@
# 阶段1: 构建
FROM golang:1.26-alpine AS builder
RUN apk add --no-cache git ca-certificates tzdata
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn,direct
ENV CGO_ENABLED=0
ENV GOTOOLCHAIN=auto
ENV GOPRIVATE=gitea.com/red-future/common
# 配置git使用私有Gitea仓库
RUN git config --global url."http://x-token-auth:9b31146aa8c10a7cb4f2e49dcee0934a223be1076289810e1ad98b968066c2bc@116.204.74.41:3000/red-future/common.git".insteadOf "https://gitea.com/red-future/common.git" && \
git config --global credential.helper store
WORKDIR /build
COPY . .
RUN go mod download && go mod tidy
RUN go build -ldflags="-s -w" -o main ./main.go
# 阶段2: 运行
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /app
COPY --from=builder /build/main .
COPY --from=builder /build/config.yml ./
RUN mkdir -p /app/resource/log/run \
/app/resource/log/server \
&& adduser -D -u 1000 appuser \
&& chown -R appuser:appuser /app
USER appuser
EXPOSE 3001
CMD ["./main"]

49
config.yml Normal file
View File

@@ -0,0 +1,49 @@
server:
address : ":3001"
name: "erp"
workerId: 1
logPath: "resource/log/server"
logStdout: true
errorStack: true
rate:
limit: 200
burst: 300
# Database.
database:
default:
- type: "pgsql"
host: "localhost"
port: "5432"
user: "postgres"
pass: "root"
name: "erp"
role: "master"
maxIdle: "5"
maxOpen: "20"
maxLifetime: "60s"
charset: "utf8mb4"
debug: true
dryRun: false
createdAt: "created_at"
updatedAt: "updated_at"
deletedAt: "deleted_at"
timeMaintainDisabled: false
redis:
# 集群模式配置方法
default:
address: 116.204.74.41:6379
db: 0
idleTimeout: "60s" #连接最大空闲时间使用时间字符串例如30s/1m/1d
maxConnLifetime: "90s" #连接最长存活时间使用时间字符串例如30s/1m/1d
waitTimeout: "60s" #等待连接池连接的超时时间使用时间字符串例如30s/1m/1d
dialTimeout: "30s" #TCP连接的超时时间使用时间字符串例如30s/1m/1d
readTimeout: "30s" #TCP的Read操作超时时间使用时间字符串例如30s/1m/1d
writeTimeout: "30s" #TCP的Write操作超时时间使用时间字符串例如30s/1m/1d
maxActive: 100
consul:
address: 116.204.74.41:8500
# pass: jiahui8888
jaeger: #链路追踪
addr: 116.204.74.41:4318

View File

@@ -0,0 +1,20 @@
package data
// AnchorStatus 主播状态
type AnchorStatus int
const (
AnchorStatusDisabled AnchorStatus = 0 // 停用
AnchorStatusActive AnchorStatus = 1 // 正常
)
func (s AnchorStatus) String() string {
switch s {
case AnchorStatusDisabled:
return "停用"
case AnchorStatusActive:
return "正常"
default:
return "未知"
}
}

View File

@@ -0,0 +1,26 @@
package data
// ScheduleStatus 排班状态
type ScheduleStatus int
const (
ScheduleStatusPending ScheduleStatus = 0 // 待直播
ScheduleStatusLive ScheduleStatus = 1 // 直播中
ScheduleStatusEnded ScheduleStatus = 2 // 已结束
ScheduleStatusCancelled ScheduleStatus = 3 // 已取消
)
func (s ScheduleStatus) String() string {
switch s {
case ScheduleStatusPending:
return "待直播"
case ScheduleStatusLive:
return "直播中"
case ScheduleStatusEnded:
return "已结束"
case ScheduleStatusCancelled:
return "已取消"
default:
return "未知"
}
}

View File

@@ -0,0 +1,9 @@
package public
// PostgreSQL表名常量
const (
AnchorTable = "anchor" // 主播表
LiveAccountTable = "live_account" // 直播账号表
ScheduleTable = "schedule" // 排班表
)

View File

@@ -0,0 +1,47 @@
package data
import (
"context"
dto "erp/model/dto/data"
service "erp/service/data"
"gitea.com/red-future/common/beans"
)
type anchorController struct{}
// Anchor 主播控制器
var Anchor = new(anchorController)
// CreateAnchor 创建主播
func (c *anchorController) CreateAnchor(ctx context.Context, req *dto.CreateAnchorReq) (res *dto.CreateAnchorRes, err error) {
return service.Anchor.Create(ctx, req)
}
// ListAnchor 获取主播列表
func (c *anchorController) ListAnchor(ctx context.Context, req *dto.ListAnchorReq) (res *dto.ListAnchorRes, err error) {
return service.Anchor.List(ctx, req)
}
// GetAnchor 获取主播详情
func (c *anchorController) GetAnchor(ctx context.Context, req *dto.GetAnchorReq) (res *dto.GetAnchorRes, err error) {
return service.Anchor.GetOne(ctx, req)
}
// UpdateAnchor 更新主播
func (c *anchorController) UpdateAnchor(ctx context.Context, req *dto.UpdateAnchorReq) (res *beans.ResponseEmpty, err error) {
err = service.Anchor.Update(ctx, req)
return
}
// UpdateAnchorStatus 更新主播状态
func (c *anchorController) UpdateAnchorStatus(ctx context.Context, req *dto.UpdateAnchorStatusReq) (res *beans.ResponseEmpty, err error) {
err = service.Anchor.UpdateStatus(ctx, req)
return
}
// DeleteAnchor 删除主播
func (c *anchorController) DeleteAnchor(ctx context.Context, req *dto.DeleteAnchorReq) (res *beans.ResponseEmpty, err error) {
err = service.Anchor.Delete(ctx, req)
return
}

View File

@@ -0,0 +1,53 @@
package data
import (
"context"
dto "erp/model/dto/data"
service "erp/service/data"
"gitea.com/red-future/common/beans"
)
type liveAccountController struct{}
// LiveAccount 直播账号控制器
var LiveAccount = new(liveAccountController)
// CreateLiveAccount 创建直播账号
func (c *liveAccountController) CreateLiveAccount(ctx context.Context, req *dto.CreateLiveAccountReq) (res *dto.CreateLiveAccountRes, err error) {
return service.LiveAccount.Create(ctx, req)
}
// ListLiveAccount 获取直播账号列表
func (c *liveAccountController) ListLiveAccount(ctx context.Context, req *dto.ListLiveAccountReq) (res *dto.ListLiveAccountRes, err error) {
return service.LiveAccount.List(ctx, req)
}
// GetLiveAccount 获取直播账号详情
func (c *liveAccountController) GetLiveAccount(ctx context.Context, req *dto.GetLiveAccountReq) (res *dto.GetLiveAccountRes, err error) {
return service.LiveAccount.GetOne(ctx, req)
}
// UpdateLiveAccount 更新直播账号
func (c *liveAccountController) UpdateLiveAccount(ctx context.Context, req *dto.UpdateLiveAccountReq) (res *beans.ResponseEmpty, err error) {
err = service.LiveAccount.Update(ctx, req)
return
}
// UpdateLiveAccountStatus 更新直播账号状态
func (c *liveAccountController) UpdateLiveAccountStatus(ctx context.Context, req *dto.UpdateLiveAccountStatusReq) (res *beans.ResponseEmpty, err error) {
err = service.LiveAccount.UpdateStatus(ctx, req)
return
}
// DeleteLiveAccount 删除直播账号
func (c *liveAccountController) DeleteLiveAccount(ctx context.Context, req *dto.DeleteLiveAccountReq) (res *beans.ResponseEmpty, err error) {
err = service.LiveAccount.Delete(ctx, req)
return
}

View File

@@ -0,0 +1,53 @@
package data
import (
"context"
dto "erp/model/dto/data"
service "erp/service/data"
"gitea.com/red-future/common/beans"
)
type scheduleController struct{}
// Schedule 排班控制器
var Schedule = new(scheduleController)
// CreateSchedule 创建排班
func (c *scheduleController) CreateSchedule(ctx context.Context, req *dto.CreateScheduleReq) (res *dto.CreateScheduleRes, err error) {
return service.Schedule.Create(ctx, req)
}
// ListSchedule 获取排班列表
func (c *scheduleController) ListSchedule(ctx context.Context, req *dto.ListScheduleReq) (res *dto.ListScheduleRes, err error) {
return service.Schedule.List(ctx, req)
}
// GetSchedule 获取排班详情
func (c *scheduleController) GetSchedule(ctx context.Context, req *dto.GetScheduleReq) (res *dto.GetScheduleRes, err error) {
return service.Schedule.GetOne(ctx, req)
}
// UpdateSchedule 更新排班
func (c *scheduleController) UpdateSchedule(ctx context.Context, req *dto.UpdateScheduleReq) (res *beans.ResponseEmpty, err error) {
err = service.Schedule.Update(ctx, req)
return
}
// UpdateScheduleStatus 更新排班状态
func (c *scheduleController) UpdateScheduleStatus(ctx context.Context, req *dto.UpdateScheduleStatusReq) (res *beans.ResponseEmpty, err error) {
err = service.Schedule.UpdateStatus(ctx, req)
return
}
// DeleteSchedule 删除排班
func (c *scheduleController) DeleteSchedule(ctx context.Context, req *dto.DeleteScheduleReq) (res *beans.ResponseEmpty, err error) {
err = service.Schedule.Delete(ctx, req)
return
}

120
dao/data/anchor_dao.go Normal file
View File

@@ -0,0 +1,120 @@
package data
import (
"context"
consts "erp/consts/public"
dto "erp/model/dto/data"
entity "erp/model/entity/data"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
var Anchor = new(anchorDao)
type anchorDao struct{}
// Insert 插入主播
func (d *anchorDao) Insert(ctx context.Context, req *dto.CreateAnchorReq) (id int64, err error) {
var res *entity.Anchor
if err = gconv.Struct(req, &res); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.AnchorTable).Data(&res).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// Update 更新主播
func (d *anchorDao) Update(ctx context.Context, req *dto.UpdateAnchorReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.AnchorTable).Data(&req).OmitEmpty().Where(entity.AnchorCols.Id, req.Id).Update()
if err != nil {
return
}
return r.RowsAffected()
}
// Delete 删除主播
func (d *anchorDao) Delete(ctx context.Context, req *dto.DeleteAnchorReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.AnchorTable).Where(entity.AnchorCols.Id, req.Id).Delete()
if err != nil {
return
}
return r.RowsAffected()
}
// GetOne 获取单个主播
func (d *anchorDao) GetOne(ctx context.Context, req *dto.GetAnchorReq) (res *entity.Anchor, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.AnchorTable).Where(entity.AnchorCols.Id, req.Id).One()
if err != nil {
return
}
err = r.Struct(&res)
return
}
// Count 获取主播数量
func (d *anchorDao) Count(ctx context.Context, req *dto.ListAnchorReq) (count int, err error) {
return d.buildListFilter(ctx, req).Count()
}
// List 获取主播列表
func (d *anchorDao) List(ctx context.Context, req *dto.ListAnchorReq) (res []entity.Anchor, total int, err error) {
model := d.buildListFilter(ctx, req)
model.OrderDesc(entity.AnchorCols.CreatedAt)
if req.Page != nil {
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
}
r, total, err := model.AllAndCount(false)
if err != nil {
return
}
err = r.Structs(&res)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *anchorDao) buildListFilter(ctx context.Context, req *dto.ListAnchorReq) *gdb.Model {
model := gfdb.DB(ctx).Model(ctx, consts.AnchorTable).Model
if !g.IsEmpty(req.Keyword) {
model.WhereLike(entity.AnchorCols.Name, "%"+req.Keyword+"%").
WhereOrLike(entity.AnchorCols.Phone, "%"+req.Keyword+"%").
WhereOrLike(entity.AnchorCols.Code, "%"+req.Keyword+"%")
}
model.Where(entity.AnchorCols.Name, req.Name)
model.Where(entity.AnchorCols.Phone, req.Phone)
model.Where(entity.AnchorCols.Code, req.Code)
if req.Status != nil {
model.Where(entity.AnchorCols.Status, *req.Status)
}
model.OmitEmptyWhere()
return model
}
// UpdateStatus 更新主播状态
func (d *anchorDao) UpdateStatus(ctx context.Context, id int64, status int) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.AnchorTable).
Data(map[string]interface{}{"status": status}).
Where(entity.AnchorCols.Id, id).
Update()
if err != nil {
return
}
return r.RowsAffected()
}
// GetByCode 根据工号获取主播
func (d *anchorDao) GetByCode(ctx context.Context, code string) (res *entity.Anchor, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.AnchorTable).
Where(entity.AnchorCols.Code, code).
One()
if err != nil {
return
}
err = r.Struct(&res)
return
}

View File

@@ -0,0 +1,120 @@
package data
import (
"context"
consts "erp/consts/public"
dto "erp/model/dto/data"
entity "erp/model/entity/data"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
var LiveAccount = new(liveAccountDao)
type liveAccountDao struct{}
// Insert 插入直播账号
func (d *liveAccountDao) Insert(ctx context.Context, req *dto.CreateLiveAccountReq) (id int64, err error) {
var res *entity.LiveAccount
if err = gconv.Struct(req, &res); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.LiveAccountTable).Data(&res).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// Update 更新直播账号
func (d *liveAccountDao) Update(ctx context.Context, req *dto.UpdateLiveAccountReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.LiveAccountTable).Data(&req).OmitEmpty().Where(entity.LiveAccountCols.Id, req.Id).Update()
if err != nil {
return
}
return r.RowsAffected()
}
// Delete 删除直播账号
func (d *liveAccountDao) Delete(ctx context.Context, req *dto.DeleteLiveAccountReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.LiveAccountTable).Where(entity.LiveAccountCols.Id, req.Id).Delete()
if err != nil {
return
}
return r.RowsAffected()
}
// GetOne 获取单个直播账号
func (d *liveAccountDao) GetOne(ctx context.Context, req *dto.GetLiveAccountReq) (res *entity.LiveAccount, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.LiveAccountTable).Where(entity.LiveAccountCols.Id, req.Id).One()
if err != nil {
return
}
err = r.Struct(&res)
return
}
// Count 获取直播账号数量
func (d *liveAccountDao) Count(ctx context.Context, req *dto.ListLiveAccountReq) (count int, err error) {
return d.buildListFilter(ctx, req).Count()
}
// List 获取直播账号列表
func (d *liveAccountDao) List(ctx context.Context, req *dto.ListLiveAccountReq) (res []entity.LiveAccount, total int, err error) {
model := d.buildListFilter(ctx, req)
model.OrderDesc(entity.LiveAccountCols.CreatedAt)
if req.Page != nil {
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
}
r, total, err := model.AllAndCount(false)
if err != nil {
return
}
err = r.Structs(&res)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *liveAccountDao) buildListFilter(ctx context.Context, req *dto.ListLiveAccountReq) *gdb.Model {
model := gfdb.DB(ctx).Model(ctx, consts.LiveAccountTable).Model
if !g.IsEmpty(req.Keyword) {
model.WhereLike(entity.LiveAccountCols.Platform, "%"+req.Keyword+"%").
WhereOrLike(entity.LiveAccountCols.AccountName, "%"+req.Keyword+"%").
WhereOrLike(entity.LiveAccountCols.AccountId, "%"+req.Keyword+"%")
}
model.Where(entity.LiveAccountCols.Platform, req.Platform)
model.Where(entity.LiveAccountCols.AccountName, req.AccountName)
model.Where(entity.LiveAccountCols.AccountId, req.AccountId)
if req.Status != nil {
model.Where(entity.LiveAccountCols.Status, *req.Status)
}
model.OmitEmptyWhere()
return model
}
// UpdateStatus 更新直播账号状态
func (d *liveAccountDao) UpdateStatus(ctx context.Context, id int64, status int) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.LiveAccountTable).
Data(map[string]interface{}{"status": status}).
Where(entity.LiveAccountCols.Id, id).
Update()
if err != nil {
return
}
return r.RowsAffected()
}
// GetByAccountId 根据账号ID获取直播账号
func (d *liveAccountDao) GetByAccountId(ctx context.Context, accountId string) (res *entity.LiveAccount, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.LiveAccountTable).
Where(entity.LiveAccountCols.AccountId, accountId).
One()
if err != nil {
return
}
err = r.Struct(&res)
return
}

140
dao/data/schedule_dao.go Normal file
View File

@@ -0,0 +1,140 @@
package data
import (
"context"
consts "erp/consts/public"
dto "erp/model/dto/data"
entity "erp/model/entity/data"
"time"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/util/gconv"
)
var Schedule = new(scheduleDao)
type scheduleDao struct{}
// Insert 插入排班
func (d *scheduleDao) Insert(ctx context.Context, req *dto.CreateScheduleReq) (id int64, err error) {
var res *entity.Schedule
if err = gconv.Struct(req, &res); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.ScheduleTable).Data(&res).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// Update 更新排班
func (d *scheduleDao) Update(ctx context.Context, req *dto.UpdateScheduleReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ScheduleTable).Data(&req).OmitEmpty().Where(entity.ScheduleCols.Id, req.Id).Update()
if err != nil {
return
}
return r.RowsAffected()
}
// Delete 删除排班
func (d *scheduleDao) Delete(ctx context.Context, req *dto.DeleteScheduleReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ScheduleTable).Where(entity.ScheduleCols.Id, req.Id).Delete()
if err != nil {
return
}
return r.RowsAffected()
}
// GetOne 获取单个排班
func (d *scheduleDao) GetOne(ctx context.Context, req *dto.GetScheduleReq) (res *entity.Schedule, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ScheduleTable).Where(entity.ScheduleCols.Id, req.Id).One()
if err != nil {
return
}
err = r.Struct(&res)
return
}
// Count 获取排班数量
func (d *scheduleDao) Count(ctx context.Context, req *dto.ListScheduleReq) (count int, err error) {
return d.buildListFilter(ctx, req).Count()
}
// List 获取排班列表
func (d *scheduleDao) List(ctx context.Context, req *dto.ListScheduleReq) (res []entity.Schedule, total int, err error) {
model := d.buildListFilter(ctx, req)
model.OrderDesc(entity.ScheduleCols.StartTime)
if req.Page != nil {
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
}
r, total, err := model.AllAndCount(false)
if err != nil {
return
}
err = r.Structs(&res)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *scheduleDao) buildListFilter(ctx context.Context, req *dto.ListScheduleReq) *gdb.Model {
model := gfdb.DB(ctx).Model(ctx, consts.ScheduleTable).Model
if req.AnchorId != nil {
model.Where(entity.ScheduleCols.AnchorId, *req.AnchorId)
}
if req.AccountId != nil {
model.Where(entity.ScheduleCols.AccountId, *req.AccountId)
}
if req.Status != nil {
model.Where(entity.ScheduleCols.Status, *req.Status)
}
if !req.StartDate.IsZero() {
model.WhereGTE(entity.ScheduleCols.StartTime, req.StartDate.Format("2006-01-02")+" 00:00:00")
}
if !req.EndDate.IsZero() {
model.WhereLTE(entity.ScheduleCols.StartTime, req.EndDate.Format("2006-01-02")+" 23:59:59")
}
return model
}
// UpdateStatus 更新排班状态
func (d *scheduleDao) UpdateStatus(ctx context.Context, id int64, status int) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ScheduleTable).
Data(map[string]interface{}{"status": status}).
Where(entity.ScheduleCols.Id, id).
Update()
if err != nil {
return
}
return r.RowsAffected()
}
// CheckTimeConflict 检查时间冲突
func (d *scheduleDao) CheckTimeConflict(ctx context.Context, anchorId int, startTime, endTime time.Time, excludeId ...int64) (count int, err error) {
model := gfdb.DB(ctx).Model(ctx, consts.ScheduleTable).
Where(entity.ScheduleCols.AnchorId, anchorId).
Where(entity.ScheduleCols.Status, 0). // 只检查待直播的排班
WhereLT(entity.ScheduleCols.StartTime, endTime.Format("2006-01-02 15:04:05")).
WhereGT(entity.ScheduleCols.EndTime, startTime.Format("2006-01-02 15:04:05"))
if len(excludeId) > 0 && excludeId[0] > 0 {
model.WhereNot(entity.ScheduleCols.Id, excludeId[0])
}
return model.Count()
}
// GetByAnchorAndTime 根据主播和时间获取排班
func (d *scheduleDao) GetByAnchorAndTime(ctx context.Context, anchorId int, startTime time.Time) (res *entity.Schedule, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ScheduleTable).
Where(entity.ScheduleCols.AnchorId, anchorId).
WhereLTE(entity.ScheduleCols.StartTime, startTime.Format("2006-01-02 15:04:05")).
WhereGTE(entity.ScheduleCols.EndTime, startTime.Format("2006-01-02 15:04:05")).
One()
if err != nil {
return
}
err = r.Struct(&res)
return
}

98
go.mod Normal file
View File

@@ -0,0 +1,98 @@
module erp
go 1.26.0
require (
gitea.com/red-future/common v0.0.12
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.10.0
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0
github.com/gogf/gf/v2 v2.10.0
golang.org/x/net v0.53.0
)
replace gitea.com/red-future/common => ../common
require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/bwmarrin/snowflake v0.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-ego/gse v1.0.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 // indirect
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang/glog v1.2.5 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/hashicorp/consul/api v1.26.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.21 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.2.0 // indirect
github.com/olekukonko/ll v0.1.8 // indirect
github.com/olekukonko/tablewriter v1.1.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/r3labs/diff/v2 v2.15.1 // indirect
github.com/redis/go-redis/v9 v9.18.0 // indirect
github.com/tiger1103/gfast-token v1.0.10 // indirect
github.com/vcaesar/cedar v0.30.0 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
go.mongodb.org/mongo-driver/v2 v2.4.0 // indirect
go.opencensus.io v0.23.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.36.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

28
main.go Normal file
View File

@@ -0,0 +1,28 @@
package main
import (
"erp/controller/data"
"gitea.com/red-future/common/http"
"gitea.com/red-future/common/jaeger"
_ "gitea.com/red-future/common/ragflow" // RAGFlow 客户端自动初始化
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
"golang.org/x/net/context"
)
func main() {
ctx := context.Background()
defer jaeger.ShutDown(ctx)
http.RouteRegister([]interface{}{
// 主播管理
data.Anchor,
// 直播账号管理
data.LiveAccount,
// 排班管理
data.Schedule,
})
select {}
}

View File

@@ -0,0 +1,89 @@
package data
import (
"erp/consts/data"
entity "erp/model/entity/data"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateAnchorReq 创建主播请求
type CreateAnchorReq struct {
g.Meta `path:"/createAnchor" method:"post" tags:"主播管理" summary:"创建主播" dc:"创建新的主播"`
Name string `json:"name" v:"required" dc:"主播姓名"`
Phone string `json:"phone" v:"required" dc:"联系电话"`
Code string `json:"code" v:"required" dc:"工号"`
Status int `json:"status" d:"1" dc:"状态0停用 1正常"`
Remark string `json:"remark" dc:"备注"`
}
// CreateAnchorRes 创建主播响应
type CreateAnchorRes struct {
Id int64 `json:"id" dc:"主播ID"`
}
// ListAnchorReq 获取主播列表请求
type ListAnchorReq struct {
g.Meta `path:"/listAnchors" method:"get" tags:"主播管理" summary:"获取主播列表" dc:"分页查询主播列表"`
*beans.Page
Name string `json:"name" dc:"主播姓名"`
Phone string `json:"phone" dc:"联系电话"`
Code string `json:"code" dc:"工号"`
Status *int `json:"status" dc:"状态0停用 1正常"`
Keyword string `json:"keyword" dc:"关键字(搜索姓名/电话/工号)"`
}
// ListAnchorRes 获取主播列表响应
type ListAnchorRes struct {
List []AnchorItem `json:"list" dc:"主播列表"`
Total int `json:"total" dc:"总数"`
}
// AnchorItem 主播列表项
type AnchorItem struct {
Id int64 `json:"id,string"`
Name string `json:"name"`
Phone string `json:"phone"`
Code string `json:"code"`
Status int `json:"status"`
StatusName string `json:"statusName"`
Remark string `json:"remark"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
// GetAnchorReq 获取主播详情请求
type GetAnchorReq struct {
g.Meta `path:"/getAnchor" method:"get" tags:"主播管理" summary:"获取主播详情" dc:"获取主播详情"`
Id int64 `json:"id" v:"required" dc:"主播ID"`
}
// GetAnchorRes 获取主播详情响应
type GetAnchorRes struct {
*entity.Anchor
}
// UpdateAnchorReq 更新主播请求
type UpdateAnchorReq struct {
g.Meta `path:"/updateAnchor" method:"put" tags:"主播管理" summary:"更新主播" dc:"更新主播信息"`
Id int64 `json:"id" v:"required" dc:"主播ID"`
Name string `json:"name" dc:"主播姓名"`
Phone string `json:"phone" dc:"联系电话"`
Code string `json:"code" dc:"工号"`
Status *int `json:"status" dc:"状态0停用 1正常"`
Remark string `json:"remark" dc:"备注"`
}
// DeleteAnchorReq 删除主播请求
type DeleteAnchorReq struct {
g.Meta `path:"/deleteAnchor" method:"delete" tags:"主播管理" summary:"删除主播" dc:"删除主播"`
Id int64 `json:"id" v:"required" dc:"主播ID"`
}
// UpdateAnchorStatusReq 更新主播状态请求
type UpdateAnchorStatusReq struct {
g.Meta `path:"/updateAnchorStatus" method:"put" tags:"主播管理" summary:"更新主播状态" dc:"更新主播状态"`
Id int64 `json:"id" v:"required" dc:"主播ID"`
Status data.AnchorStatus `json:"status" v:"required" dc:"状态0停用 1正常"`
}

View File

@@ -0,0 +1,89 @@
package data
import (
"erp/consts/data"
entity "erp/model/entity/data"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateLiveAccountReq 创建直播账号请求
type CreateLiveAccountReq struct {
g.Meta `path:"/createLiveAccount" method:"post" tags:"直播账号管理" summary:"创建直播账号" dc:"创建新的直播账号"`
Platform string `json:"platform" v:"required" dc:"直播平台(抖音/快手/淘宝等)"`
AccountName string `json:"accountName" v:"required" dc:"账号名称"`
AccountId string `json:"accountId" v:"required" dc:"账号ID"`
Status int `json:"status" d:"1" dc:"状态0停用 1正常"`
Remark string `json:"remark" dc:"备注"`
}
// CreateLiveAccountRes 创建直播账号响应
type CreateLiveAccountRes struct {
Id int64 `json:"id" dc:"账号ID"`
}
// ListLiveAccountReq 获取直播账号列表请求
type ListLiveAccountReq struct {
g.Meta `path:"/listLiveAccounts" method:"get" tags:"直播账号管理" summary:"获取直播账号列表" dc:"分页查询直播账号列表"`
*beans.Page
Platform string `json:"platform" dc:"直播平台"`
AccountName string `json:"accountName" dc:"账号名称"`
AccountId string `json:"accountId" dc:"账号ID"`
Status *int `json:"status" dc:"状态0停用 1正常"`
Keyword string `json:"keyword" dc:"关键字(搜索平台/账号名称/账号ID"`
}
// ListLiveAccountRes 获取直播账号列表响应
type ListLiveAccountRes struct {
List []LiveAccountItem `json:"list" dc:"直播账号列表"`
Total int `json:"total" dc:"总数"`
}
// LiveAccountItem 直播账号列表项
type LiveAccountItem struct {
Id int64 `json:"id,string"`
Platform string `json:"platform"`
AccountName string `json:"accountName"`
AccountId string `json:"accountId"`
Status int `json:"status"`
StatusName string `json:"statusName"`
Remark string `json:"remark"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
// GetLiveAccountReq 获取直播账号详情请求
type GetLiveAccountReq struct {
g.Meta `path:"/getLiveAccount" method:"get" tags:"直播账号管理" summary:"获取直播账号详情" dc:"获取直播账号详情"`
Id int64 `json:"id" v:"required" dc:"账号ID"`
}
// GetLiveAccountRes 获取直播账号详情响应
type GetLiveAccountRes struct {
*entity.LiveAccount
}
// UpdateLiveAccountReq 更新直播账号请求
type UpdateLiveAccountReq struct {
g.Meta `path:"/updateLiveAccount" method:"put" tags:"直播账号管理" summary:"更新直播账号" dc:"更新直播账号信息"`
Id int64 `json:"id" v:"required" dc:"账号ID"`
Platform string `json:"platform" dc:"直播平台"`
AccountName string `json:"accountName" dc:"账号名称"`
AccountId string `json:"accountId" dc:"账号ID"`
Status *int `json:"status" dc:"状态0停用 1正常"`
Remark string `json:"remark" dc:"备注"`
}
// DeleteLiveAccountReq 删除直播账号请求
type DeleteLiveAccountReq struct {
g.Meta `path:"/deleteLiveAccount" method:"delete" tags:"直播账号管理" summary:"删除直播账号" dc:"删除直播账号"`
Id int64 `json:"id" v:"required" dc:"账号ID"`
}
// UpdateLiveAccountStatusReq 更新直播账号状态请求
type UpdateLiveAccountStatusReq struct {
g.Meta `path:"/updateLiveAccountStatus" method:"put" tags:"直播账号管理" summary:"更新直播账号状态" dc:"更新直播账号状态"`
Id int64 `json:"id" v:"required" dc:"账号ID"`
Status data.AnchorStatus `json:"status" v:"required" dc:"状态0停用 1正常"`
}

View File

@@ -0,0 +1,102 @@
package data
import (
"erp/consts/data"
entity "erp/model/entity/data"
"time"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateScheduleReq 创建排班请求
type CreateScheduleReq struct {
g.Meta `path:"/createSchedule" method:"post" tags:"排班管理" summary:"创建排班" dc:"创建新的排班"`
AnchorId int `json:"anchorId" v:"required" dc:"主播ID"`
AccountId int `json:"accountId" v:"required" dc:"直播账号ID"`
StartTime time.Time `json:"startTime" v:"required" dc:"开始时间"`
EndTime time.Time `json:"endTime" v:"required" dc:"结束时间"`
Status int `json:"status" d:"0" dc:"状态0待直播 1直播中 2已结束 3已取消"`
ProductId int64 `json:"productId" dc:"商品ID"`
OrderId int64 `json:"orderId" dc:"订单ID"`
Remark string `json:"remark" dc:"备注"`
}
// CreateScheduleRes 创建排班响应
type CreateScheduleRes struct {
Id int64 `json:"id" dc:"排班ID"`
}
// ListScheduleReq 获取排班列表请求
type ListScheduleReq struct {
g.Meta `path:"/listSchedules" method:"get" tags:"排班管理" summary:"获取排班列表" dc:"分页查询排班列表"`
*beans.Page
AnchorId *int `json:"anchorId" dc:"主播ID"`
AccountId *int `json:"accountId" dc:"直播账号ID"`
Status *int `json:"status" dc:"状态"`
StartDate time.Time `json:"startDate" dc:"开始日期(筛选)"`
EndDate time.Time `json:"endDate" dc:"结束日期(筛选)"`
}
// ListScheduleRes 获取排班列表响应
type ListScheduleRes struct {
List []ScheduleItem `json:"list" dc:"排班列表"`
Total int `json:"total" dc:"总数"`
}
// ScheduleItem 排班列表项
type ScheduleItem struct {
Id int64 `json:"id,string"`
AnchorId int `json:"anchorId"`
AnchorName string `json:"anchorName"`
AccountId int `json:"accountId"`
AccountName string `json:"accountName"`
Platform string `json:"platform"`
StartTime int64 `json:"startTime"`
EndTime int64 `json:"endTime"`
Status int `json:"status"`
StatusName string `json:"statusName"`
ProductId int64 `json:"productId"`
OrderId int64 `json:"orderId"`
Remark string `json:"remark"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
// GetScheduleReq 获取排班详情请求
type GetScheduleReq struct {
g.Meta `path:"/getSchedule" method:"get" tags:"排班管理" summary:"获取排班详情" dc:"获取排班详情"`
Id int64 `json:"id" v:"required" dc:"排班ID"`
}
// GetScheduleRes 获取排班详情响应
type GetScheduleRes struct {
*entity.Schedule
}
// UpdateScheduleReq 更新排班请求
type UpdateScheduleReq struct {
g.Meta `path:"/updateSchedule" method:"put" tags:"排班管理" summary:"更新排班" dc:"更新排班信息"`
Id int64 `json:"id" v:"required" dc:"排班ID"`
AnchorId *int `json:"anchorId" dc:"主播ID"`
AccountId *int `json:"accountId" dc:"直播账号ID"`
StartTime *time.Time `json:"startTime" dc:"开始时间"`
EndTime *time.Time `json:"endTime" dc:"结束时间"`
Status *int `json:"status" dc:"状态0待直播 1直播中 2已结束 3已取消"`
ProductId *int64 `json:"productId" dc:"商品ID"`
OrderId *int64 `json:"orderId" dc:"订单ID"`
Remark string `json:"remark" dc:"备注"`
}
// DeleteScheduleReq 删除排班请求
type DeleteScheduleReq struct {
g.Meta `path:"/deleteSchedule" method:"delete" tags:"排班管理" summary:"删除排班" dc:"删除排班"`
Id int64 `json:"id" v:"required" dc:"排班ID"`
}
// UpdateScheduleStatusReq 更新排班状态请求
type UpdateScheduleStatusReq struct {
g.Meta `path:"/updateScheduleStatus" method:"put" tags:"排班管理" summary:"更新排班状态" dc:"更新排班状态"`
Id int64 `json:"id" v:"required" dc:"排班ID"`
Status data.ScheduleStatus `json:"status" v:"required" dc:"状态0待直播 1直播中 2已结束 3已取消"`
}

View File

@@ -0,0 +1,35 @@
package data
import (
"gitea.com/red-future/common/beans"
)
// Anchor 主播实体
type Anchor struct {
beans.SQLBaseDO `orm:",inherit"`
Name string `orm:"name" json:"name" description:"主播姓名"`
Phone string `orm:"phone" json:"phone" description:"联系电话"`
Code string `orm:"code" json:"code" description:"工号"`
Status int `orm:"status" json:"status" description:"状态0停用 1正常"`
Remark string `orm:"remark" json:"remark" description:"备注"`
}
// AnchorCol 主播表字段定义
type AnchorCol struct {
beans.SQLBaseCol
Name string
Phone string
Code string
Status string
Remark string
}
// AnchorCols 主播表字段常量
var AnchorCols = AnchorCol{
SQLBaseCol: beans.DefSQLBaseCol,
Name: "name",
Phone: "phone",
Code: "code",
Status: "status",
Remark: "remark",
}

View File

@@ -0,0 +1,35 @@
package data
import (
"gitea.com/red-future/common/beans"
)
// LiveAccount 直播账号实体
type LiveAccount struct {
beans.SQLBaseDO `orm:",inherit"`
Platform string `orm:"platform" json:"platform" description:"直播平台(抖音/快手/淘宝等)"`
AccountName string `orm:"account_name" json:"accountName" description:"账号名称"`
AccountId string `orm:"account_id" json:"accountId" description:"账号ID"`
Status int `orm:"status" json:"status" description:"状态0停用 1正常"`
Remark string `orm:"remark" json:"remark" description:"备注"`
}
// LiveAccountCol 直播账号表字段定义
type LiveAccountCol struct {
beans.SQLBaseCol
Platform string
AccountName string
AccountId string
Status string
Remark string
}
// LiveAccountCols 直播账号表字段常量
var LiveAccountCols = LiveAccountCol{
SQLBaseCol: beans.DefSQLBaseCol,
Platform: "platform",
AccountName: "account_name",
AccountId: "account_id",
Status: "status",
Remark: "remark",
}

View File

@@ -0,0 +1,46 @@
package data
import (
"time"
"gitea.com/red-future/common/beans"
)
// Schedule 排班实体
type Schedule struct {
beans.SQLBaseDO `orm:",inherit"`
AnchorId int `orm:"anchor_id" json:"anchorId" description:"主播ID"`
AccountId int `orm:"account_id" json:"accountId" description:"直播账号ID"`
StartTime time.Time `orm:"start_time" json:"startTime" description:"开始时间"`
EndTime time.Time `orm:"end_time" json:"endTime" description:"结束时间"`
Status int `orm:"status" json:"status" description:"状态0待直播 1直播中 2已结束 3已取消"`
ProductId int64 `orm:"product_id" json:"productId" description:"商品ID"`
OrderId int64 `orm:"order_id" json:"orderId" description:"订单ID"`
Remark string `orm:"remark" json:"remark" description:"备注"`
}
// ScheduleCol 排班表字段定义
type ScheduleCol struct {
beans.SQLBaseCol
AnchorId string
AccountId string
StartTime string
EndTime string
Status string
ProductId string
OrderId string
Remark string
}
// ScheduleCols 排班表字段常量
var ScheduleCols = ScheduleCol{
SQLBaseCol: beans.DefSQLBaseCol,
AnchorId: "anchor_id",
AccountId: "account_id",
StartTime: "start_time",
EndTime: "end_time",
Status: "status",
ProductId: "product_id",
OrderId: "order_id",
Remark: "remark",
}

View File

@@ -0,0 +1,133 @@
package data
import (
"context"
consts "erp/consts/data"
dao "erp/dao/data"
dto "erp/model/dto/data"
entity "erp/model/entity/data"
"errors"
"github.com/gogf/gf/v2/util/gconv"
)
type anchorService struct{}
// Anchor 主播服务
var Anchor = new(anchorService)
// Create 创建主播
func (s *anchorService) Create(ctx context.Context, req *dto.CreateAnchorReq) (res *dto.CreateAnchorRes, err error) {
// 检查工号是否重复
existAnchor, err := dao.Anchor.GetByCode(ctx, req.Code)
if err != nil {
return nil, err
}
if existAnchor != nil {
return nil, errors.New("工号已存在")
}
// 插入数据库
id, err := dao.Anchor.Insert(ctx, req)
if err != nil {
return
}
res = &dto.CreateAnchorRes{
Id: id,
}
return
}
// List 获取主播列表
func (s *anchorService) List(ctx context.Context, req *dto.ListAnchorReq) (res *dto.ListAnchorRes, err error) {
anchorList, total, err := dao.Anchor.List(ctx, req)
if err != nil {
return
}
// 组装响应数据
list := make([]dto.AnchorItem, 0, len(anchorList))
for _, item := range anchorList {
list = append(list, dto.AnchorItem{
Id: item.Id,
Name: item.Name,
Phone: item.Phone,
Code: item.Code,
Status: item.Status,
StatusName: consts.AnchorStatus(item.Status).String(),
Remark: item.Remark,
CreatedAt: item.CreatedAt.Unix(),
UpdatedAt: item.UpdatedAt.Unix(),
})
}
res = &dto.ListAnchorRes{
List: list,
Total: total,
}
return
}
// GetOne 获取单个主播
func (s *anchorService) GetOne(ctx context.Context, req *dto.GetAnchorReq) (res *dto.GetAnchorRes, err error) {
anchor, err := dao.Anchor.GetOne(ctx, req)
if err != nil {
return
}
var anchorEntity *entity.Anchor
if err = gconv.Struct(anchor, &anchorEntity); err != nil {
return
}
return &dto.GetAnchorRes{
Anchor: anchorEntity,
}, nil
}
// Update 更新主播
func (s *anchorService) Update(ctx context.Context, req *dto.UpdateAnchorReq) (err error) {
// 检查主播是否存在
exist, err := dao.Anchor.GetOne(ctx, &dto.GetAnchorReq{Id: req.Id})
if err != nil || exist == nil {
return errors.New("主播不存在")
}
// 如果修改了工号,检查新工号是否重复
if req.Code != "" && req.Code != exist.Code {
existAnchor, err := dao.Anchor.GetByCode(ctx, req.Code)
if err != nil {
return err
}
if existAnchor != nil {
return errors.New("工号已存在")
}
}
_, err = dao.Anchor.Update(ctx, req)
return
}
// UpdateStatus 更新主播状态
func (s *anchorService) UpdateStatus(ctx context.Context, req *dto.UpdateAnchorStatusReq) (err error) {
_, err = dao.Anchor.UpdateStatus(ctx, req.Id, int(req.Status))
return
}
// Delete 删除主播
func (s *anchorService) Delete(ctx context.Context, req *dto.DeleteAnchorReq) (err error) {
// 检查是否存在关联的排班
anchorId := int(req.Id)
schedules, _, err := dao.Schedule.List(ctx, &dto.ListScheduleReq{
AnchorId: &anchorId,
})
if err != nil {
return err
}
if len(schedules) > 0 {
return errors.New("该主播存在排班记录,无法删除")
}
_, err = dao.Anchor.Delete(ctx, req)
return
}

View File

@@ -0,0 +1,133 @@
package data
import (
"context"
consts "erp/consts/data"
dao "erp/dao/data"
dto "erp/model/dto/data"
entity "erp/model/entity/data"
"errors"
"github.com/gogf/gf/v2/util/gconv"
)
type liveAccountService struct{}
// LiveAccount 直播账号服务
var LiveAccount = new(liveAccountService)
// Create 创建直播账号
func (s *liveAccountService) Create(ctx context.Context, req *dto.CreateLiveAccountReq) (res *dto.CreateLiveAccountRes, err error) {
// 检查账号ID是否重复
existAccount, err := dao.LiveAccount.GetByAccountId(ctx, req.AccountId)
if err != nil {
return nil, err
}
if existAccount != nil {
return nil, errors.New("账号ID已存在")
}
// 插入数据库
id, err := dao.LiveAccount.Insert(ctx, req)
if err != nil {
return
}
res = &dto.CreateLiveAccountRes{
Id: id,
}
return
}
// List 获取直播账号列表
func (s *liveAccountService) List(ctx context.Context, req *dto.ListLiveAccountReq) (res *dto.ListLiveAccountRes, err error) {
accountList, total, err := dao.LiveAccount.List(ctx, req)
if err != nil {
return
}
// 组装响应数据
list := make([]dto.LiveAccountItem, 0, len(accountList))
for _, item := range accountList {
list = append(list, dto.LiveAccountItem{
Id: item.Id,
Platform: item.Platform,
AccountName: item.AccountName,
AccountId: item.AccountId,
Status: item.Status,
StatusName: consts.AnchorStatus(item.Status).String(),
Remark: item.Remark,
CreatedAt: item.CreatedAt.Unix(),
UpdatedAt: item.UpdatedAt.Unix(),
})
}
res = &dto.ListLiveAccountRes{
List: list,
Total: total,
}
return
}
// GetOne 获取单个直播账号
func (s *liveAccountService) GetOne(ctx context.Context, req *dto.GetLiveAccountReq) (res *dto.GetLiveAccountRes, err error) {
account, err := dao.LiveAccount.GetOne(ctx, req)
if err != nil {
return
}
var accountEntity *entity.LiveAccount
if err = gconv.Struct(account, &accountEntity); err != nil {
return
}
return &dto.GetLiveAccountRes{
LiveAccount: accountEntity,
}, nil
}
// Update 更新直播账号
func (s *liveAccountService) Update(ctx context.Context, req *dto.UpdateLiveAccountReq) (err error) {
// 检查直播账号是否存在
exist, err := dao.LiveAccount.GetOne(ctx, &dto.GetLiveAccountReq{Id: req.Id})
if err != nil || exist == nil {
return errors.New("直播账号不存在")
}
// 如果修改了账号ID检查新账号ID是否重复
if req.AccountId != "" && req.AccountId != exist.AccountId {
existAccount, err := dao.LiveAccount.GetByAccountId(ctx, req.AccountId)
if err != nil {
return err
}
if existAccount != nil {
return errors.New("账号ID已存在")
}
}
_, err = dao.LiveAccount.Update(ctx, req)
return
}
// UpdateStatus 更新直播账号状态
func (s *liveAccountService) UpdateStatus(ctx context.Context, req *dto.UpdateLiveAccountStatusReq) (err error) {
_, err = dao.LiveAccount.UpdateStatus(ctx, req.Id, int(req.Status))
return
}
// Delete 删除直播账号
func (s *liveAccountService) Delete(ctx context.Context, req *dto.DeleteLiveAccountReq) (err error) {
// 检查是否存在关联的排班
accountId := int(req.Id)
schedules, _, err := dao.Schedule.List(ctx, &dto.ListScheduleReq{
AccountId: &accountId,
})
if err != nil {
return err
}
if len(schedules) > 0 {
return errors.New("该账号存在排班记录,无法删除")
}
_, err = dao.LiveAccount.Delete(ctx, req)
return
}

View File

@@ -0,0 +1,226 @@
package data
import (
"context"
consts "erp/consts/data"
dao "erp/dao/data"
dto "erp/model/dto/data"
entity "erp/model/entity/data"
"errors"
"time"
"github.com/gogf/gf/v2/util/gconv"
)
type scheduleService struct{}
// Schedule 排班服务
var Schedule = new(scheduleService)
// Create 创建排班
func (s *scheduleService) Create(ctx context.Context, req *dto.CreateScheduleReq) (res *dto.CreateScheduleRes, err error) {
// 检查开始时间是否小于结束时间
if req.StartTime.After(req.EndTime) || req.StartTime.Equal(req.EndTime) {
return nil, errors.New("开始时间必须早于结束时间")
}
// 检查主播是否存在
_, err = dao.Anchor.GetOne(ctx, &dto.GetAnchorReq{Id: int64(req.AnchorId)})
if err != nil {
return nil, errors.New("主播不存在")
}
// 检查直播账号是否存在
_, err = dao.LiveAccount.GetOne(ctx, &dto.GetLiveAccountReq{Id: int64(req.AccountId)})
if err != nil {
return nil, errors.New("直播账号不存在")
}
// 检查时间冲突
conflictCount, err := dao.Schedule.CheckTimeConflict(ctx, req.AnchorId, req.StartTime, req.EndTime)
if err != nil {
return nil, err
}
if conflictCount > 0 {
return nil, errors.New("该主播在此时间段已有排班")
}
// 插入数据库
id, err := dao.Schedule.Insert(ctx, req)
if err != nil {
return
}
res = &dto.CreateScheduleRes{
Id: id,
}
return
}
// List 获取排班列表
func (s *scheduleService) List(ctx context.Context, req *dto.ListScheduleReq) (res *dto.ListScheduleRes, err error) {
scheduleList, total, err := dao.Schedule.List(ctx, req)
if err != nil {
return
}
// 组装响应数据
list := make([]dto.ScheduleItem, 0, len(scheduleList))
for _, item := range scheduleList {
// 获取主播信息
anchorName := ""
anchor, _ := dao.Anchor.GetOne(ctx, &dto.GetAnchorReq{Id: int64(item.AnchorId)})
if anchor != nil {
anchorName = anchor.Name
}
// 获取账号信息
accountName := ""
platform := ""
account, _ := dao.LiveAccount.GetOne(ctx, &dto.GetLiveAccountReq{Id: int64(item.AccountId)})
if account != nil {
accountName = account.AccountName
platform = account.Platform
}
list = append(list, dto.ScheduleItem{
Id: item.Id,
AnchorId: item.AnchorId,
AnchorName: anchorName,
AccountId: item.AccountId,
AccountName: accountName,
Platform: platform,
StartTime: item.StartTime.Unix(),
EndTime: item.EndTime.Unix(),
Status: item.Status,
StatusName: consts.ScheduleStatus(item.Status).String(),
ProductId: item.ProductId,
OrderId: item.OrderId,
Remark: item.Remark,
CreatedAt: item.CreatedAt.Unix(),
UpdatedAt: item.UpdatedAt.Unix(),
})
}
res = &dto.ListScheduleRes{
List: list,
Total: total,
}
return
}
// GetOne 获取单个排班
func (s *scheduleService) GetOne(ctx context.Context, req *dto.GetScheduleReq) (res *dto.GetScheduleRes, err error) {
schedule, err := dao.Schedule.GetOne(ctx, req)
if err != nil {
return
}
var scheduleEntity *entity.Schedule
if err = gconv.Struct(schedule, &scheduleEntity); err != nil {
return
}
return &dto.GetScheduleRes{
Schedule: scheduleEntity,
}, nil
}
// Update 更新排班
func (s *scheduleService) Update(ctx context.Context, req *dto.UpdateScheduleReq) (err error) {
// 检查排班是否存在
exist, err := dao.Schedule.GetOne(ctx, &dto.GetScheduleReq{Id: req.Id})
if err != nil || exist == nil {
return errors.New("排班不存在")
}
// 如果修改了时间,检查时间合法性
startTime := exist.StartTime
endTime := exist.EndTime
if req.StartTime != nil {
startTime = *req.StartTime
}
if req.EndTime != nil {
endTime = *req.EndTime
}
if startTime.After(endTime) || startTime.Equal(endTime) {
return errors.New("开始时间必须早于结束时间")
}
// 如果修改了主播或时间,检查时间冲突
anchorId := exist.AnchorId
if req.AnchorId != nil {
anchorId = *req.AnchorId
// 检查主播是否存在
_, err = dao.Anchor.GetOne(ctx, &dto.GetAnchorReq{Id: int64(anchorId)})
if err != nil {
return errors.New("主播不存在")
}
}
conflictCount, err := dao.Schedule.CheckTimeConflict(ctx, anchorId, startTime, endTime, req.Id)
if err != nil {
return err
}
if conflictCount > 0 {
return errors.New("该主播在此时间段已有排班")
}
// 如果修改了账号,检查账号是否存在
if req.AccountId != nil {
_, err = dao.LiveAccount.GetOne(ctx, &dto.GetLiveAccountReq{Id: int64(*req.AccountId)})
if err != nil {
return errors.New("直播账号不存在")
}
}
_, err = dao.Schedule.Update(ctx, req)
return
}
// UpdateStatus 更新排班状态
func (s *scheduleService) UpdateStatus(ctx context.Context, req *dto.UpdateScheduleStatusReq) (err error) {
_, err = dao.Schedule.UpdateStatus(ctx, req.Id, int(req.Status))
return
}
// Delete 删除排班
func (s *scheduleService) Delete(ctx context.Context, req *dto.DeleteScheduleReq) (err error) {
// 检查排班是否存在且未开始
schedule, err := dao.Schedule.GetOne(ctx, &dto.GetScheduleReq{Id: req.Id})
if err != nil || schedule == nil {
return errors.New("排班不存在")
}
// 如果已经开始或结束,不允许删除
if schedule.Status != 0 {
return errors.New("只能删除待直播的排班")
}
_, err = dao.Schedule.Delete(ctx, req)
return
}
// GetTodaySchedules 获取今日排班
func (s *scheduleService) GetTodaySchedules(ctx context.Context) (res *dto.ListScheduleRes, err error) {
now := time.Now()
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location())
req := &dto.ListScheduleReq{
StartDate: startOfDay,
EndDate: endOfDay,
}
return s.List(ctx, req)
}
// GetAnchorSchedules 获取主播排班
func (s *scheduleService) GetAnchorSchedules(ctx context.Context, anchorId int, startDate, endDate time.Time) (res *dto.ListScheduleRes, err error) {
req := &dto.ListScheduleReq{
AnchorId: &anchorId,
StartDate: startDate,
EndDate: endDate,
}
return s.List(ctx, req)
}

160
update.sql Normal file
View File

@@ -0,0 +1,160 @@
-- auto-generated definition
create table schedule
(
id bigserial
primary key,
anchor_id bigint default 0 not null,
account_id bigint default 0 not null,
start_time timestamp not null,
end_time timestamp not null,
status smallint default 0 not null,
remark varchar(500) default ''::character varying not null,
created_by bigint default 0 not null,
updated_by bigint default 0 not null,
created_at timestamp default CURRENT_TIMESTAMP not null,
updated_at timestamp default CURRENT_TIMESTAMP not null,
deleted_at timestamp,
tenant_id bigint default 0,
product_id bigint default 0,
order_id bigint default 0
);
comment on table schedule is '排班表';
comment on column schedule.id is '排班ID';
comment on column schedule.anchor_id is '主播ID';
comment on column schedule.account_id is '直播账号ID';
comment on column schedule.start_time is '开始时间';
comment on column schedule.end_time is '结束时间';
comment on column schedule.status is '状态0待直播 1直播中 2已结束 3已取消';
comment on column schedule.remark is '备注';
comment on column schedule.created_by is '创建人';
comment on column schedule.updated_by is '修改人';
comment on column schedule.created_at is '创建时间';
comment on column schedule.updated_at is '修改时间';
comment on column schedule.deleted_at is '删除时间';
comment on column schedule.product_id is '商品ID';
comment on column schedule.order_id is '订单ID';
alter table schedule
owner to postgres;
create index idx_schedule_tenant_id
on schedule (tenant_id);
-- auto-generated definition
create table anchor
(
id bigint default nextval('anchor_id_seq'::regclass) not null
primary key,
name varchar(50) default ''::character varying not null,
phone varchar(20) default ''::character varying not null,
code varchar(255) default ''::character varying not null,
status smallint default 1 not null,
remark varchar(500) default ''::character varying not null,
created_by bigint default 0 not null,
updated_by bigint default 0 not null,
created_at timestamp default CURRENT_TIMESTAMP not null,
updated_at timestamp default CURRENT_TIMESTAMP not null,
deleted_at timestamp,
tenant_id bigint default 0
);
comment on table anchor is '主播表';
comment on column anchor.id is '主播ID';
comment on column anchor.name is '主播姓名';
comment on column anchor.phone is '联系电话';
comment on column anchor.code is '工号';
comment on column anchor.status is '状态0停用 1正常';
comment on column anchor.remark is '备注';
comment on column anchor.created_by is '创建人';
comment on column anchor.updated_by is '修改人';
comment on column anchor.created_at is '创建时间';
comment on column anchor.updated_at is '修改时间';
comment on column anchor.deleted_at is '删除时间';
alter table anchor
owner to postgres;
create index idx_anchor_tenant_id
on anchor (tenant_id);
-- auto-generated definition
create table live_account
(
id bigint default nextval('live_account_id_seq'::regclass) not null
primary key,
platform varchar(50) default ''::character varying not null,
account_name varchar(100) default ''::character varying not null,
account_id varchar(100) default ''::character varying not null,
status smallint default 1 not null,
remark varchar(500) default ''::character varying not null,
created_by bigint default 0 not null,
updated_by bigint default 0 not null,
created_at timestamp default CURRENT_TIMESTAMP not null,
updated_at timestamp default CURRENT_TIMESTAMP not null,
deleted_at timestamp,
tenant_id bigint default 0
);
comment on table live_account is '直播账号表';
comment on column live_account.id is '账号ID';
comment on column live_account.platform is '直播平台(抖音/快手/淘宝等)';
comment on column live_account.account_name is '账号名称';
comment on column live_account.account_id is '账号ID';
comment on column live_account.status is '状态0停用 1正常';
comment on column live_account.remark is '备注';
comment on column live_account.created_by is '创建人';
comment on column live_account.updated_by is '修改人';
comment on column live_account.created_at is '创建时间';
comment on column live_account.updated_at is '修改时间';
comment on column live_account.deleted_at is '删除时间';
alter table live_account
owner to postgres;
create index idx_live_account_tenant_id
on live_account (tenant_id);