commit adb6da1d70b2aef95c4b89b31d9249a702864894 Author: lmk <1095689763@qq.com> Date: Fri Apr 17 16:28:31 2026 +0800 排班管理、主播管理、直播账号管理 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1b7204 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea/* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d8bff7b --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..fa93799 --- /dev/null +++ b/config.yml @@ -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 \ No newline at end of file diff --git a/consts/data/AnchorStatus.go b/consts/data/AnchorStatus.go new file mode 100644 index 0000000..96b75b7 --- /dev/null +++ b/consts/data/AnchorStatus.go @@ -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 "未知" + } +} diff --git a/consts/data/ScheduleStatus.go b/consts/data/ScheduleStatus.go new file mode 100644 index 0000000..ac2d53a --- /dev/null +++ b/consts/data/ScheduleStatus.go @@ -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 "未知" + } +} diff --git a/consts/public/collections.go b/consts/public/collections.go new file mode 100644 index 0000000..25a1169 --- /dev/null +++ b/consts/public/collections.go @@ -0,0 +1,9 @@ +package public + +// PostgreSQL表名常量 + +const ( + AnchorTable = "anchor" // 主播表 + LiveAccountTable = "live_account" // 直播账号表 + ScheduleTable = "schedule" // 排班表 +) diff --git a/controller/data/anchor_controller.go b/controller/data/anchor_controller.go new file mode 100644 index 0000000..ccdf842 --- /dev/null +++ b/controller/data/anchor_controller.go @@ -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 +} diff --git a/controller/data/live_account_controller.go b/controller/data/live_account_controller.go new file mode 100644 index 0000000..0bfad60 --- /dev/null +++ b/controller/data/live_account_controller.go @@ -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 +} diff --git a/controller/data/schedule_controller.go b/controller/data/schedule_controller.go new file mode 100644 index 0000000..84a93af --- /dev/null +++ b/controller/data/schedule_controller.go @@ -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 +} diff --git a/dao/data/anchor_dao.go b/dao/data/anchor_dao.go new file mode 100644 index 0000000..5d44bf3 --- /dev/null +++ b/dao/data/anchor_dao.go @@ -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 +} diff --git a/dao/data/live_account_dao.go b/dao/data/live_account_dao.go new file mode 100644 index 0000000..f5aae1b --- /dev/null +++ b/dao/data/live_account_dao.go @@ -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 +} diff --git a/dao/data/schedule_dao.go b/dao/data/schedule_dao.go new file mode 100644 index 0000000..9665db9 --- /dev/null +++ b/dao/data/schedule_dao.go @@ -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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..db579f1 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/main.go b/main.go new file mode 100644 index 0000000..9f2b248 --- /dev/null +++ b/main.go @@ -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 {} +} diff --git a/model/dto/data/anchor_dto.go b/model/dto/data/anchor_dto.go new file mode 100644 index 0000000..5216c74 --- /dev/null +++ b/model/dto/data/anchor_dto.go @@ -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正常)"` +} diff --git a/model/dto/data/live_account_dto.go b/model/dto/data/live_account_dto.go new file mode 100644 index 0000000..7af9cfe --- /dev/null +++ b/model/dto/data/live_account_dto.go @@ -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正常)"` +} diff --git a/model/dto/data/schedule_dto.go b/model/dto/data/schedule_dto.go new file mode 100644 index 0000000..051aebf --- /dev/null +++ b/model/dto/data/schedule_dto.go @@ -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已取消)"` +} diff --git a/model/entity/data/anchor.go b/model/entity/data/anchor.go new file mode 100644 index 0000000..53d12b4 --- /dev/null +++ b/model/entity/data/anchor.go @@ -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", +} diff --git a/model/entity/data/live_account.go b/model/entity/data/live_account.go new file mode 100644 index 0000000..ad10c1a --- /dev/null +++ b/model/entity/data/live_account.go @@ -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", +} diff --git a/model/entity/data/schedule.go b/model/entity/data/schedule.go new file mode 100644 index 0000000..dea8b2c --- /dev/null +++ b/model/entity/data/schedule.go @@ -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", +} diff --git a/service/data/anchor_service.go b/service/data/anchor_service.go new file mode 100644 index 0000000..a514c2b --- /dev/null +++ b/service/data/anchor_service.go @@ -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 +} diff --git a/service/data/live_account_service.go b/service/data/live_account_service.go new file mode 100644 index 0000000..9c19190 --- /dev/null +++ b/service/data/live_account_service.go @@ -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 +} diff --git a/service/data/schedule_service.go b/service/data/schedule_service.go new file mode 100644 index 0000000..494f0fa --- /dev/null +++ b/service/data/schedule_service.go @@ -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) +} diff --git a/update.sql b/update.sql new file mode 100644 index 0000000..e03c90d --- /dev/null +++ b/update.sql @@ -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); + +