完成视频案例后端

This commit is contained in:
2025-10-31 15:01:09 +08:00
parent d11263a45b
commit 4e9736a3b4
6 changed files with 301 additions and 12 deletions

View File

@@ -5,6 +5,8 @@ package video_case
import ( import (
"context" "context"
"database/sql"
"fmt"
"github.com/JACKYMYPERSON/hldrCenter/config" "github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/video_case/internal/model" "github.com/JACKYMYPERSON/hldrCenter/internal/video_case/internal/model"
@@ -30,7 +32,51 @@ func NewCreateVideoCaseLogic(ctx context.Context, cfg *config.Config, model mode
} }
func (l *CreateVideoCaseLogic) CreateVideoCase(req *types.CreateVideoCaseReq) (resp *types.BaseResp, err error) { func (l *CreateVideoCaseLogic) CreateVideoCase(req *types.CreateVideoCaseReq) (resp *types.BaseResp, err error) {
// todo: add your logic here and delete this line // 1. 二次参数校验补充validate标签之外的必填项检查
if req.Title == "" {
return return &types.BaseResp{Code: 400, Msg: "视频案例标题为必填项"}, nil
}
if req.VideoUrl == "" {
return &types.BaseResp{Code: 400, Msg: "视频播放地址为必填项"}, nil
}
if req.DesignerNames == "" {
return &types.BaseResp{Code: 400, Msg: "设计人员名单为必填项"}, nil
}
if req.TutorNames == "" {
return &types.BaseResp{Code: 400, Msg: "指导老师名单为必填项"}, nil
}
// 2. 映射请求参数到model结构体设置软删除默认值
data := &model.VideoCase{
Title: req.Title,
Intro: StringToNullString(req.Intro), // 允许为空,使用默认空字符串
VideoUrl: req.VideoUrl,
DesignerNames: req.DesignerNames,
TutorNames: req.TutorNames,
Sort: int64(req.Sort), // 默认为0无需额外处理
IsDelete: 0, // 软删除默认值未删除0
// create_time和update_time由数据库自动维护无需手动赋值
}
// 3. 调用model插入数据
_, err = l.model.Insert(l.ctx, data)
if err != nil {
// 4. 数据库错误处理:返回具体错误信息
return &types.BaseResp{
Code: 500,
Msg: fmt.Sprintf("创建视频案例失败:%s", err.Error()),
}, err
}
// 5. 成功响应
return &types.BaseResp{
Code: 0,
Msg: "视频案例创建成功",
}, nil
}
func StringToNullString(s string) sql.NullString {
if s != "" {
return sql.NullString{String: s, Valid: true}
}
return sql.NullString{Valid: false}
} }

View File

@@ -5,6 +5,7 @@ package video_case
import ( import (
"context" "context"
"fmt"
"github.com/JACKYMYPERSON/hldrCenter/config" "github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/video_case/internal/model" "github.com/JACKYMYPERSON/hldrCenter/internal/video_case/internal/model"
@@ -30,7 +31,29 @@ func NewDeleteVideoCaseLogic(ctx context.Context, cfg *config.Config, model mode
} }
func (l *DeleteVideoCaseLogic) DeleteVideoCase(req *types.DeleteVideoCaseReq) (resp *types.BaseResp, err error) { func (l *DeleteVideoCaseLogic) DeleteVideoCase(req *types.DeleteVideoCaseReq) (resp *types.BaseResp, err error) {
// todo: add your logic here and delete this line // 1. 参数校验确保ID合法
if req.Id <= 0 {
return return &types.BaseResp{Code: 400, Msg: "视频案例ID不合法"}, nil
}
id := int64(req.Id) // 转换为int64与Model层参数类型一致
// 2. 检查视频案例是否存在且未被删除
_, err = l.model.FindOne(l.ctx, id)
if err != nil {
// 若案例不存在或已删除FindOne已过滤is_delete=1的记录
if err == model.ErrNotFound {
return &types.BaseResp{Code: 404, Msg: "视频案例不存在或已删除"}, nil
}
// 其他查询错误
return &types.BaseResp{Code: 500, Msg: fmt.Sprintf("查询视频案例失败:%s", err.Error())}, err
}
// 3. 执行软删除更新is_delete为1
err = l.model.Delete(l.ctx, id)
if err != nil {
return &types.BaseResp{Code: 500, Msg: fmt.Sprintf("删除视频案例失败:%s", err.Error())}, err
}
// 4. 成功响应
return &types.BaseResp{Code: 0, Msg: "视频案例删除成功"}, nil
} }

View File

@@ -5,6 +5,8 @@ package video_case
import ( import (
"context" "context"
"errors"
"fmt"
"github.com/JACKYMYPERSON/hldrCenter/config" "github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/video_case/internal/model" "github.com/JACKYMYPERSON/hldrCenter/internal/video_case/internal/model"
@@ -30,7 +32,51 @@ func NewGetVideoCaseLogic(ctx context.Context, cfg *config.Config, model model.V
} }
func (l *GetVideoCaseLogic) GetVideoCase(req *types.GetVideoCaseReq) (resp *types.VideoCaseResp, err error) { func (l *GetVideoCaseLogic) GetVideoCase(req *types.GetVideoCaseReq) (resp *types.VideoCaseResp, err error) {
// todo: add your logic here and delete this line // 1. 参数校验确保ID合法
if req.Id <= 0 {
return return nil, errors.New("视频案例ID不合法")
}
id := int64(req.Id) // 转换为int64与Model层参数类型一致
// 2. 调用Model查询过滤已删除数据需先修改Model的FindOne方法
modelCase, err := l.model.FindOne(l.ctx, id)
if err != nil {
// 区分"未找到"和"查询异常"
if err == model.ErrNotFound {
return nil, errors.New("视频案例不存在或已删除")
}
return nil, fmt.Errorf("查询视频案例失败:%w", err)
}
// 3. 处理特殊类型转换
// 3.1 Intro字段sql.NullString → stringValid为true时取值否则为空
intro := ""
if modelCase.Intro.Valid {
intro = modelCase.Intro.String
}
// 3.2 时间字段time.Time → string统一格式
createTime := ""
if !modelCase.CreateTime.IsZero() {
createTime = modelCase.CreateTime.Format("2006-01-02 15:04:05")
}
updateTime := ""
if !modelCase.UpdateTime.IsZero() {
updateTime = modelCase.UpdateTime.Format("2006-01-02 15:04:05")
}
// 4. 映射Model数据到响应结构体
resp = &types.VideoCaseResp{
Id: int(modelCase.Id),
Title: modelCase.Title,
Intro: intro, // 转换后的简介
VideoUrl: modelCase.VideoUrl,
DesignerNames: modelCase.DesignerNames,
TutorNames: modelCase.TutorNames,
Sort: int(modelCase.Sort),
CreateTime: createTime, // 转换后的创建时间
UpdateTime: updateTime, // 转换后的更新时间
}
return resp, nil
} }

View File

@@ -5,6 +5,7 @@ package video_case
import ( import (
"context" "context"
"fmt"
"github.com/JACKYMYPERSON/hldrCenter/config" "github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/video_case/internal/model" "github.com/JACKYMYPERSON/hldrCenter/internal/video_case/internal/model"
@@ -30,7 +31,66 @@ func NewListVideoCaseLogic(ctx context.Context, cfg *config.Config, model model.
} }
func (l *ListVideoCaseLogic) ListVideoCase(req *types.ListVideoCaseReq) (resp *types.ListVideoCaseResp, err error) { func (l *ListVideoCaseLogic) ListVideoCase(req *types.ListVideoCaseReq) (resp *types.ListVideoCaseResp, err error) {
// todo: add your logic here and delete this line // 1. 参数校验与默认值处理
if req.Page <= 0 {
return req.Page = 1 // 默认第一页
}
if req.Size <= 0 || req.Size > 100 {
req.Size = 10 // 限制每页最大100条默认10条
}
// 2. 分页计算offset = (页码-1) * 每页条数
offset := (req.Page - 1) * req.Size
// 3. 调用Model层查询先查总条数再查分页数据
total, err := l.model.Count(l.ctx, req.Keyword)
if err != nil {
return nil, fmt.Errorf("查询视频案例总条数失败:%w", err)
}
list, err := l.model.ListByPage(l.ctx, req.Keyword, req.Sort, offset, req.Size)
if err != nil {
return nil, fmt.Errorf("查询视频案例列表失败:%w", err)
}
// 4. 数据映射Model → 响应结构体(处理特殊类型转换)
respList := make([]types.VideoCaseResp, 0, len(list))
for _, item := range list {
// 4.1 处理Intro字段sql.NullString → string
intro := ""
if item.Intro.Valid {
intro = item.Intro.String
}
// 4.2 处理时间字段time.Time → string统一格式
createTime := ""
if !item.CreateTime.IsZero() {
createTime = item.CreateTime.Format("2006-01-02 15:04:05")
}
updateTime := ""
if !item.UpdateTime.IsZero() {
updateTime = item.UpdateTime.Format("2006-01-02 15:04:05")
}
// 4.3 组装单条响应数据
respList = append(respList, types.VideoCaseResp{
Id: int(item.Id),
Title: item.Title,
Intro: intro,
VideoUrl: item.VideoUrl,
DesignerNames: item.DesignerNames,
TutorNames: item.TutorNames,
Sort: int(item.Sort),
CreateTime: createTime,
UpdateTime: updateTime,
})
}
// 5. 组装最终响应
resp = &types.ListVideoCaseResp{
Total: total,
List: respList,
}
return resp, nil
} }

View File

@@ -5,6 +5,7 @@ package video_case
import ( import (
"context" "context"
"fmt"
"github.com/JACKYMYPERSON/hldrCenter/config" "github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/video_case/internal/model" "github.com/JACKYMYPERSON/hldrCenter/internal/video_case/internal/model"
@@ -30,7 +31,62 @@ func NewUpdateVideoCaseLogic(ctx context.Context, cfg *config.Config, model mode
} }
func (l *UpdateVideoCaseLogic) UpdateVideoCase(req *types.UpdateVideoCaseReq) (resp *types.BaseResp, err error) { func (l *UpdateVideoCaseLogic) UpdateVideoCase(req *types.UpdateVideoCaseReq) (resp *types.BaseResp, err error) {
// todo: add your logic here and delete this line // 1. 参数校验ID为必填且合法
if req.Id <= 0 {
return return &types.BaseResp{Code: 400, Msg: "视频案例ID不合法"}, nil
}
id := int64(req.Id)
// 2. 校验案例是否存在且未被删除
existCase, err := l.model.FindOne(l.ctx, id)
if err != nil {
if err == model.ErrNotFound {
return &types.BaseResp{Code: 404, Msg: "视频案例不存在或已删除"}, nil
}
return &types.BaseResp{Code: 500, Msg: fmt.Sprintf("查询视频案例失败:%s", err.Error())}, err
}
// 3. 部分字段更新:只更新请求中提供的非空/有效值(保留原有数据)
updateData := &model.VideoCase{
Id: existCase.Id, // 必须保留ID更新条件
IsDelete: existCase.IsDelete, // 锁定软删除状态(不允许修改)
Title: existCase.Title,
Intro: existCase.Intro, // 初始化为原有值sql.NullString类型
VideoUrl: existCase.VideoUrl,
DesignerNames: existCase.DesignerNames,
TutorNames: existCase.TutorNames,
Sort: existCase.Sort,
}
// 覆盖请求中传递的非空字段(注意类型转换)
if req.Title != "" {
updateData.Title = req.Title
}
if req.VideoUrl != "" {
updateData.VideoUrl = req.VideoUrl
}
if req.DesignerNames != "" {
updateData.DesignerNames = req.DesignerNames
}
if req.TutorNames != "" {
updateData.TutorNames = req.TutorNames
}
// 处理Intro字段string → sql.NullString
if req.Intro != "" || (req.Intro == "" && existCase.Intro.Valid) {
// 若请求传空字符串或原有值非空均需更新覆盖为NULL或新值
updateData.Intro = StringToNullString(req.Intro)
}
// 处理Sort字段0是合法值允许主动设置
if req.Sort != 0 {
updateData.Sort = int64(req.Sort)
}
// 4. 调用Model层执行更新
err = l.model.Update(l.ctx, updateData)
if err != nil {
return &types.BaseResp{Code: 500, Msg: fmt.Sprintf("更新视频案例失败:%s", err.Error())}, err
}
// 5. 成功响应
return &types.BaseResp{Code: 0, Msg: "视频案例更新成功"}, nil
} }

View File

@@ -29,6 +29,8 @@ type (
FindOne(ctx context.Context, id int64) (*VideoCase, error) FindOne(ctx context.Context, id int64) (*VideoCase, error)
Update(ctx context.Context, data *VideoCase) error Update(ctx context.Context, data *VideoCase) error
Delete(ctx context.Context, id int64) error Delete(ctx context.Context, id int64) error
Count(ctx context.Context, keyword string) (int64, error)
ListByPage(ctx context.Context, keyword string, sort int, offset, size int) ([]*VideoCase, error)
} }
defaultVideoCaseModel struct { defaultVideoCaseModel struct {
@@ -58,13 +60,14 @@ func newVideoCaseModel(conn sqlx.SqlConn) *defaultVideoCaseModel {
} }
func (m *defaultVideoCaseModel) Delete(ctx context.Context, id int64) error { func (m *defaultVideoCaseModel) Delete(ctx context.Context, id int64) error {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table) query := fmt.Sprintf("update %s set `is_delete` = 1, `update_time` = CURRENT_TIMESTAMP where `id` = ? and `is_delete` = 0", m.table)
_, err := m.conn.ExecCtx(ctx, query, id) _, err := m.conn.ExecCtx(ctx, query, id)
return err return err
} }
func (m *defaultVideoCaseModel) FindOne(ctx context.Context, id int64) (*VideoCase, error) { func (m *defaultVideoCaseModel) FindOne(ctx context.Context, id int64) (*VideoCase, error) {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", videoCaseRows, m.table) // 关键仅查询未删除的案例is_delete=0
query := fmt.Sprintf("select %s from %s where `id` = ? and `is_delete` = 0 limit 1", videoCaseRows, m.table)
var resp VideoCase var resp VideoCase
err := m.conn.QueryRowCtx(ctx, &resp, query, id) err := m.conn.QueryRowCtx(ctx, &resp, query, id)
switch err { switch err {
@@ -89,6 +92,61 @@ func (m *defaultVideoCaseModel) Update(ctx context.Context, data *VideoCase) err
return err return err
} }
func (m *defaultVideoCaseModel) Count(ctx context.Context, keyword string) (int64, error) {
query := fmt.Sprintf("select count(id) from %s where is_delete = 0", m.table)
args := make([]interface{}, 0)
// 关键词搜索:模糊匹配标题、设计人员、指导老师
if keyword != "" {
keyword = strings.TrimSpace(keyword)
query += " and (title like ? or designer_names like ? or tutor_names like ?)"
likeVal := "%" + keyword + "%"
args = append(args, likeVal, likeVal, likeVal) // 三个字段共用一个模糊值
}
var total int64
// sqlx.QueryRowCtx自动扫描结果到total无需额外Scan
err := m.conn.QueryRowCtx(ctx, &total, query, args...)
if err != nil {
return 0, err
}
return total, nil
}
// ListByPage 分页查询未删除的视频案例列表(支持关键词、排序)
func (m *defaultVideoCaseModel) ListByPage(ctx context.Context, keyword string, sort int, offset, size int) ([]*VideoCase, error) {
query := fmt.Sprintf("select %s from %s where is_delete = 0", videoCaseRows, m.table)
args := make([]interface{}, 0)
// 关键词搜索条件与Count方法一致
if keyword != "" {
keyword = strings.TrimSpace(keyword)
query += " and (title like ? or designer_names like ? or tutor_names like ?)"
likeVal := "%" + keyword + "%"
args = append(args, likeVal, likeVal, likeVal)
}
// 排序逻辑优先按sort升序数字越小越靠前否则按创建时间降序
query += " order by "
if sort != 0 {
query += "sort asc, create_time desc"
} else {
query += "create_time desc"
}
// 分页条件
query += " limit ?, ?"
args = append(args, offset, size)
// 执行查询
var list []*VideoCase
err := m.conn.QueryRowsCtx(ctx, &list, query, args...)
if err != nil {
return nil, err
}
return list, nil
}
func (m *defaultVideoCaseModel) tableName() string { func (m *defaultVideoCaseModel) tableName() string {
return m.table return m.table
} }