完成speaker后端

This commit is contained in:
2025-10-29 11:09:43 +08:00
parent 70ca97bff2
commit b7309dec47
10 changed files with 301 additions and 48 deletions

View File

@@ -9,6 +9,7 @@ import (
"github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/logic/meeting_speaker"
model2 "github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/meetingmodel"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/model"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/types"
"github.com/zeromicro/go-zero/core/stores/sqlx"
@@ -39,7 +40,9 @@ func CreateSpeakerHandler(cfg *config.Config) http.HandlerFunc {
speakerModel := model.NewMeetingSpeakerModel(conn)
l := meeting_speaker.NewCreateSpeakerLogic(r.Context(), cfg, speakerModel)
meetingModel := model2.NewMeetingModel(conn)
l := meeting_speaker.NewCreateSpeakerLogic(r.Context(), cfg, speakerModel, meetingModel)
resp, err := l.CreateSpeaker(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)

View File

@@ -6,6 +6,8 @@ package meeting_speaker
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/logic/meeting_speaker"
@@ -18,10 +20,18 @@ import (
func DeleteSpeakerHandler(cfg *config.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.DeleteSpeakerReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
pathParts := strings.Split(r.URL.Path, "/")
if len(pathParts) < 3 { // 确保路径格式正确
httpx.ErrorCtx(r.Context(), w, fmt.Errorf("invalid path format"))
return
}
idStr := pathParts[3]
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
httpx.ErrorCtx(r.Context(), w, fmt.Errorf("invalid meeting ID"))
return
}
req.Id = id
mysqlCfg := cfg.MySQL
dsn := fmt.Sprintf(

View File

@@ -9,6 +9,7 @@ import (
"github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/logic/meeting_speaker"
model2 "github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/meetingmodel"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/model"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/types"
"github.com/zeromicro/go-zero/core/stores/sqlx"
@@ -39,7 +40,9 @@ func ListSpeakersByMeetingHandler(cfg *config.Config) http.HandlerFunc {
speakerModel := model.NewMeetingSpeakerModel(conn)
l := meeting_speaker.NewListSpeakersByMeetingLogic(r.Context(), cfg, speakerModel)
meetingModel := model2.NewMeetingModel(conn)
l := meeting_speaker.NewListSpeakersByMeetingLogic(r.Context(), cfg, speakerModel, meetingModel)
resp, err := l.ListSpeakersByMeeting(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)

View File

@@ -9,6 +9,7 @@ import (
"github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/logic/meeting_speaker"
model2 "github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/meetingmodel"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/model"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/types"
"github.com/zeromicro/go-zero/core/stores/sqlx"
@@ -39,7 +40,9 @@ func UpdateSpeakerHandler(cfg *config.Config) http.HandlerFunc {
speakerModel := model.NewMeetingSpeakerModel(conn)
l := meeting_speaker.NewUpdateSpeakerLogic(r.Context(), cfg, speakerModel)
meetingModel := model2.NewMeetingModel(conn)
l := meeting_speaker.NewUpdateSpeakerLogic(r.Context(), cfg, speakerModel, meetingModel)
resp, err := l.UpdateSpeaker(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)

View File

@@ -5,8 +5,11 @@ package meeting_speaker
import (
"context"
"database/sql"
"errors"
"github.com/JACKYMYPERSON/hldrCenter/config"
model2 "github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/meetingmodel"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/model"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/types"
@@ -18,19 +21,83 @@ type CreateSpeakerLogic struct {
ctx context.Context
cfg *config.Config
model model.MeetingSpeakerModel
meetingmodel model2.MeetingModel
}
func NewCreateSpeakerLogic(ctx context.Context, cfg *config.Config, model model.MeetingSpeakerModel) *CreateSpeakerLogic {
func NewCreateSpeakerLogic(ctx context.Context, cfg *config.Config, model model.MeetingSpeakerModel, meetingmodel model2.MeetingModel) *CreateSpeakerLogic {
return &CreateSpeakerLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
cfg: cfg,
model: model,
meetingmodel: meetingmodel,
}
}
func (l *CreateSpeakerLogic) CreateSpeaker(req *types.CreateSpeakerReq) (resp *types.CreateSpeakerResp, err error) {
// todo: add your logic here and delete this line
return
// 1. 基础参数二次校验(补充 validate 未覆盖的业务逻辑)
if req.MeetingId <= 0 {
return nil, errors.New("会议ID无效需传入正整数")
}
if len(req.Name) == 0 || len(req.Name) > 100 {
return nil, errors.New("嘉宾姓名必填且长度不能超过100字")
}
if req.Title != "" && len(req.Title) > 200 {
return nil, errors.New("嘉宾头衔长度不能超过200字")
}
if req.Avatar != "" && len(req.Avatar) > 512 {
return nil, errors.New("头像URL长度不能超过512字符")
}
// 排序字段默认0未传时设为0符合非负整数要求
if req.Sort < 0 {
return nil, errors.New("排序值不能为负数")
}
// 2. 校验关联会议是否存在(且未被删除)
_, err = l.meetingmodel.FindOne(l.ctx, req.MeetingId)
if err != nil {
if err == model.ErrNotFound {
return nil, errors.New("关联的会议不存在或已被删除")
}
l.Logger.Errorf("查询关联会议失败MeetingId: %d%v", req.MeetingId, err)
return nil, errors.New("创建嘉宾失败,请重试")
}
// 3. 构造数据库模型数据(字段一一对应)
speaker := &model.MeetingSpeaker{
MeetingId: req.MeetingId,
Name: req.Name,
Title: req.Title, // 可选字段,空字符串直接存储
Avatar: req.Avatar,
Intro: sql.NullString{
String: req.Intro, // 赋值实际字符串
Valid: req.Intro != "", // 若不为空字符串,则设为有效(存实际值);若为空,则设为无效(存 NULL
}, // 长文本字段,支持空值
Sort: int64(req.Sort), // 未传时默认0符合业务预期
// 若模型包含 CreateTime/UpdateTime/IsDelete无需手动赋值数据库自动处理
}
// 4. 处理特殊字段(若模型中 Intro 为 sql.NullString 类型,需转换)
// 注:根据 Insert 方法参数推断 Intro 为 string 类型,若为 sql.NullString 则替换为:
// Intro: sql.NullString{String: req.Intro, Valid: req.Intro != ""},
// 5. 调用模型层插入嘉宾数据
result, err := l.model.Insert(l.ctx, speaker)
if err != nil {
l.Logger.Errorf("创建会议嘉宾失败MeetingId: %d, Name: %s%v", req.MeetingId, req.Name, err)
return nil, errors.New("创建嘉宾失败,请重试")
}
// 6. 获取新增嘉宾的主键ID用于响应返回
speakerId, err := result.LastInsertId()
if err != nil {
l.Logger.Errorf("获取嘉宾ID失败MeetingId: %d, Name: %s%v", req.MeetingId, req.Name, err)
return nil, errors.New("嘉宾创建成功但获取ID失败")
}
// 7. 构造响应结果
return &types.CreateSpeakerResp{
Msg: "会议嘉宾创建成功",
SpeakerId: speakerId,
}, nil
}

View File

@@ -5,6 +5,7 @@ package meeting_speaker
import (
"context"
"errors"
"github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/model"
@@ -30,7 +31,35 @@ func NewDeleteSpeakerLogic(ctx context.Context, cfg *config.Config, model model.
}
func (l *DeleteSpeakerLogic) DeleteSpeaker(req *types.DeleteSpeakerReq) (resp *types.DeleteSpeakerResp, err error) {
// todo: add your logic here and delete this line
return
// 1. 校验嘉宾ID有效性
if req.Id <= 0 {
return nil, errors.New("嘉宾ID无效请传入正整数")
}
// 2. 检查嘉宾是否存在(且未被删除)
_, err = l.model.FindOne(l.ctx, req.Id)
if err != nil {
if err == model.ErrNotFound {
return nil, errors.New("嘉宾不存在或已被删除")
}
l.Logger.Errorf("查询嘉宾失败ID: %d%v", req.Id, err)
return nil, errors.New("删除嘉宾失败,请重试")
}
// 3. 执行软删除操作
rowsAffected, err := l.model.SoftDelete(l.ctx, req.Id)
if err != nil {
l.Logger.Errorf("软删除嘉宾失败ID: %d%v", req.Id, err)
return nil, errors.New("删除嘉宾失败,请重试")
}
// 4. 确认删除生效(避免并发删除导致未生效)
if rowsAffected == 0 {
return nil, errors.New("删除失败,嘉宾可能已被删除")
}
// 5. 构造响应
return &types.DeleteSpeakerResp{
Msg: "嘉宾已软删除成功",
}, nil
}

View File

@@ -30,7 +30,6 @@ func NewGetSpeakerLogic(ctx context.Context, cfg *config.Config, model model.Mee
}
func (l *GetSpeakerLogic) GetSpeaker(req *types.GetSpeakerReq) (resp *types.GetSpeakerResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@@ -5,8 +5,11 @@ package meeting_speaker
import (
"context"
"database/sql"
"errors"
"github.com/JACKYMYPERSON/hldrCenter/config"
model2 "github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/meetingmodel"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/model"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/types"
@@ -18,19 +21,78 @@ type ListSpeakersByMeetingLogic struct {
ctx context.Context
cfg *config.Config
model model.MeetingSpeakerModel
meetingmodel model2.MeetingModel
}
func NewListSpeakersByMeetingLogic(ctx context.Context, cfg *config.Config, model model.MeetingSpeakerModel) *ListSpeakersByMeetingLogic {
func NewListSpeakersByMeetingLogic(ctx context.Context, cfg *config.Config, model model.MeetingSpeakerModel, meetingmodel model2.MeetingModel) *ListSpeakersByMeetingLogic {
return &ListSpeakersByMeetingLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
cfg: cfg,
model: model,
meetingmodel: meetingmodel,
}
}
func (l *ListSpeakersByMeetingLogic) ListSpeakersByMeeting(req *types.ListSpeakersByMeetingReq) (resp *types.ListSpeakersByMeetingResp, err error) {
// todo: add your logic here and delete this line
return
// 1. 校验请求参数有效性
// 1.1 会议ID校验
if req.MeetingId <= 0 {
return nil, errors.New("会议ID无效请传入正整数")
}
// 1.2 分页参数校验(设置默认值,避免非法值)
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 || pageSize > 100 { // 限制最大100条/页,避免性能问题
pageSize = 10
}
// 2. 校验关联会议是否存在(避免查询不存在的会议的嘉宾)
_, err = l.meetingmodel.FindOne(l.ctx, req.MeetingId)
if err != nil {
if err == model.ErrNotFound {
return nil, errors.New("关联的会议不存在或已被删除")
}
l.Logger.Errorf("查询关联会议失败MeetingId: %d%v", req.MeetingId, err)
return nil, errors.New("查询嘉宾列表失败,请重试")
}
// 3. 调用模型层查询嘉宾列表(分页+软删除过滤+排序)
speakers, total, err := l.model.ListByMeetingId(l.ctx, req.MeetingId, page, pageSize)
if err != nil {
l.Logger.Errorf("查询会议嘉宾列表失败MeetingId: %d, page: %d%v", req.MeetingId, page, err)
return nil, errors.New("查询嘉宾列表失败,请重试")
}
// 4. 转换模型数据为响应格式(适配前端展示)
var speakerDetails []*types.SpeakerDetail
for _, speaker := range speakers {
speakerDetails = append(speakerDetails, &types.SpeakerDetail{
Id: speaker.Id,
MeetingId: speaker.MeetingId,
Name: speaker.Name,
Title: speaker.Title,
Avatar: speaker.Avatar,
Intro: getNullStringValue(speaker.Intro),
Sort: int(speaker.Sort),
})
}
// 5. 构造响应(包含分页信息,方便前端渲染分页控件)
return &types.ListSpeakersByMeetingResp{
Msg: "查询嘉宾列表成功", // 注意:结构体 json tag 为 "message",序列化后前端获取 key 为 "message"
Total: total, // 该会议下未删除的嘉宾总条数
Speakers: speakerDetails, // 当前页嘉宾列表(按 sort 升序)
}, nil
}
// 工具函数:处理 sql.NullString 类型(若模型中 Intro 为该类型则启用)
func getNullStringValue(nullStr sql.NullString) string {
if nullStr.Valid {
return nullStr.String
}
return ""
}

View File

@@ -5,8 +5,11 @@ package meeting_speaker
import (
"context"
"database/sql"
"errors"
"github.com/JACKYMYPERSON/hldrCenter/config"
model2 "github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/meetingmodel"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/model"
"github.com/JACKYMYPERSON/hldrCenter/internal/meetingspeaker/internal/types"
@@ -18,19 +21,94 @@ type UpdateSpeakerLogic struct {
ctx context.Context
cfg *config.Config
model model.MeetingSpeakerModel
meetingmodel model2.MeetingModel
}
func NewUpdateSpeakerLogic(ctx context.Context, cfg *config.Config, model model.MeetingSpeakerModel) *UpdateSpeakerLogic {
func NewUpdateSpeakerLogic(ctx context.Context, cfg *config.Config, model model.MeetingSpeakerModel, meetingmodel model2.MeetingModel) *UpdateSpeakerLogic {
return &UpdateSpeakerLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
cfg: cfg,
model: model,
meetingmodel: meetingmodel,
}
}
func (l *UpdateSpeakerLogic) UpdateSpeaker(req *types.UpdateSpeakerReq) (resp *types.UpdateSpeakerResp, err error) {
// todo: add your logic here and delete this line
return
// 1. 基础参数校验(严格对齐字段类型与校验规则)
// 1.1 嘉宾ID必填且有效
if req.Id <= 0 {
return nil, errors.New("嘉宾ID无效请传入正整数")
}
// 1.2 姓名传入则需非空且长度≤100字适配 validate:min=1,max=100
if req.Name != "" && len(req.Name) > 100 {
return nil, errors.New("嘉宾姓名长度不能超过100字")
}
// 1.4 排序传入则需非负req.Sort为int转int64适配模型
if req.Sort < 0 {
return nil, errors.New("排序值不能为负数")
}
// 1.5 头衔/头像URL长度限制
if len(req.Title) > 200 {
return nil, errors.New("嘉宾头衔长度不能超过200字")
}
if len(req.Avatar) > 512 {
return nil, errors.New("头像URL长度不能超过512字符")
}
// 2. 校验嘉宾是否存在(未被软删除)
existingSpeaker, err := l.model.FindOne(l.ctx, req.Id)
if err != nil {
if err == model.ErrNotFound {
return nil, errors.New("嘉宾不存在或已被删除,无法更新")
}
l.Logger.Errorf("查询嘉宾失败ID: %d%v", req.Id, err)
return nil, errors.New("更新嘉宾失败,请重试")
}
// 4. 构造更新数据(仅更新用户明确传入的有效字段,对齐模型字段类型)
updateData := &model.MeetingSpeaker{
Id: req.Id,
MeetingId: existingSpeaker.MeetingId,
Name: existingSpeaker.Name,
Title: existingSpeaker.Title,
Avatar: existingSpeaker.Avatar,
Sort: existingSpeaker.Sort, // 模型中为int64
Intro: existingSpeaker.Intro, // 模型中为sql.NullString
}
// 4.1 覆盖用户主动更新的字段(非默认值即视为更新)
if req.Name != "" { // 非空表示主动更新姓名(已通过非空校验)
updateData.Name = req.Name
}
if req.Title != "" { // 非空表示主动更新头衔
updateData.Title = req.Title
}
if req.Avatar != "" { // 非空表示主动更新头像URL
updateData.Avatar = req.Avatar
}
if req.Sort != 0 { // 非0表示主动更新排序int转int64适配模型
updateData.Sort = int64(req.Sort)
}
// 4.2 处理Introsql.NullString类型与原始值不同则更新含主动传空字符串
if req.Intro != existingSpeaker.Intro.String {
updateData.Intro = sql.NullString{
String: req.Intro,
Valid: true, // 主动传入即标记为有效(空字符串也视为有效数据)
}
// 若业务需将空字符串存为数据库NULL可改为
// updateData.Intro.Valid = req.Intro != ""
}
// 5. 执行更新操作调用模型层Update方法字段顺序完全匹配
err = l.model.Update(l.ctx, updateData)
if err != nil {
l.Logger.Errorf("更新嘉宾失败ID: %d%v", req.Id, err)
return nil, errors.New("更新嘉宾失败,请重试")
}
// 6. 构造响应
return &types.UpdateSpeakerResp{
Msg: "嘉宾信息更新成功",
}, nil
}

View File

@@ -35,9 +35,9 @@ type GetSpeakerResp struct {
}
type ListSpeakersByMeetingReq struct {
MeetingId int64 `path:"meetingId" validate:"required,min=1"` // 会议ID从路径参数获取必填
Page int `query:"page" validate:"min=1"` // 页码默认1
PageSize int `query:"page_size" validate:"min=1,max=100"` // 每页条数默认10最大100
MeetingId int64 `json:"meetingId" validate:"required,min=1"` // 会议ID从路径参数获取必填
Page int `json:"page" validate:"min=1"` // 页码默认1
PageSize int `json:"page_size" validate:"min=1,max=100"` // 每页条数默认10最大100
}
type ListSpeakersByMeetingResp struct {
@@ -59,7 +59,6 @@ type SpeakerDetail struct {
type UpdateSpeakerReq struct {
Id int64 `json:"id" validate:"required,min=1"` // 嘉宾ID必填定位要更新的记录
MeetingId int64 `json:"meeting_id,omitempty" validate:"min=1"` // 关联会议ID可选不填则不更新
Name string `json:"name,omitempty" validate:"min=1,max=100"` // 嘉宾姓名(可选,更新时需非空)
Title string `json:"title,omitempty" validate:"max=200"` // 嘉宾头衔(可选)
Avatar string `json:"avatar,omitempty" validate:"max=512"` // 头像URL可选