diff --git a/server/internal/meeting/handler/meeting/createmeetinghandler.go b/server/internal/meeting/handler/meeting/createmeetinghandler.go new file mode 100644 index 00000000..6b29a87e --- /dev/null +++ b/server/internal/meeting/handler/meeting/createmeetinghandler.go @@ -0,0 +1,50 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package meeting + +import ( + "fmt" + "net/http" + + "github.com/JACKYMYPERSON/hldrCenter/config" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/logic/meeting" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/model" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/types" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/rest/httpx" +) + +func CreateMeetingHandler(cfg *config.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.CreateMeetingReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + mysqlCfg := cfg.MySQL + dsn := fmt.Sprintf( + "%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true&loc=Local", + mysqlCfg.Username, + mysqlCfg.Password, + mysqlCfg.Host, + mysqlCfg.Port, + mysqlCfg.Database, + mysqlCfg.Charset, + ) + fmt.Println("接收到articlePost请求") + + conn := sqlx.NewSqlConn("mysql", dsn) + + meetingModel := model.NewMeetingModel(conn) + + l := meeting.NewCreateMeetingLogic(r.Context(), cfg, meetingModel) + resp, err := l.CreateMeeting(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/server/internal/meeting/handler/meeting/deletemeetinghandler.go b/server/internal/meeting/handler/meeting/deletemeetinghandler.go new file mode 100644 index 00000000..c2557500 --- /dev/null +++ b/server/internal/meeting/handler/meeting/deletemeetinghandler.go @@ -0,0 +1,60 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package meeting + +import ( + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/JACKYMYPERSON/hldrCenter/config" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/logic/meeting" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/model" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/types" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/rest/httpx" +) + +func DeleteMeetingHandler(cfg *config.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.DeleteMeetingReq + 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( + "%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true&loc=Local", + mysqlCfg.Username, + mysqlCfg.Password, + mysqlCfg.Host, + mysqlCfg.Port, + mysqlCfg.Database, + mysqlCfg.Charset, + ) + fmt.Println("接收到articlePost请求") + + conn := sqlx.NewSqlConn("mysql", dsn) + + meetingModel := model.NewMeetingModel(conn) + + l := meeting.NewDeleteMeetingLogic(r.Context(), cfg, meetingModel) + resp, err := l.DeleteMeeting(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/server/internal/meeting/handler/meeting/getmeetinghandler.go b/server/internal/meeting/handler/meeting/getmeetinghandler.go new file mode 100644 index 00000000..30f93068 --- /dev/null +++ b/server/internal/meeting/handler/meeting/getmeetinghandler.go @@ -0,0 +1,61 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package meeting + +import ( + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/JACKYMYPERSON/hldrCenter/config" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/logic/meeting" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/model" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/types" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/rest/httpx" +) + +func GetMeetingHandler(cfg *config.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetMeetingReq + // 正确解析路径(例如:/meetings/1) + 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( + "%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true&loc=Local", + mysqlCfg.Username, + mysqlCfg.Password, + mysqlCfg.Host, + mysqlCfg.Port, + mysqlCfg.Database, + mysqlCfg.Charset, + ) + fmt.Println("接收到articlePost请求") + + conn := sqlx.NewSqlConn("mysql", dsn) + + meetingModel := model.NewMeetingModel(conn) + + l := meeting.NewGetMeetingLogic(r.Context(), cfg, meetingModel) + resp, err := l.GetMeeting(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/server/internal/meeting/handler/meeting/listmeetinghandler.go b/server/internal/meeting/handler/meeting/listmeetinghandler.go new file mode 100644 index 00000000..63b826f2 --- /dev/null +++ b/server/internal/meeting/handler/meeting/listmeetinghandler.go @@ -0,0 +1,50 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package meeting + +import ( + "fmt" + "net/http" + + "github.com/JACKYMYPERSON/hldrCenter/config" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/logic/meeting" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/model" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/types" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/rest/httpx" +) + +func ListMeetingHandler(cfg *config.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.ListMeetingReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + mysqlCfg := cfg.MySQL + dsn := fmt.Sprintf( + "%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true&loc=Local", + mysqlCfg.Username, + mysqlCfg.Password, + mysqlCfg.Host, + mysqlCfg.Port, + mysqlCfg.Database, + mysqlCfg.Charset, + ) + fmt.Println("接收到articlePost请求") + + conn := sqlx.NewSqlConn("mysql", dsn) + + meetingModel := model.NewMeetingModel(conn) + + l := meeting.NewListMeetingLogic(r.Context(), cfg, meetingModel) + resp, err := l.ListMeeting(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/server/internal/meeting/handler/meeting/updatemeetinghandler.go b/server/internal/meeting/handler/meeting/updatemeetinghandler.go new file mode 100644 index 00000000..fb375cad --- /dev/null +++ b/server/internal/meeting/handler/meeting/updatemeetinghandler.go @@ -0,0 +1,50 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package meeting + +import ( + "fmt" + "net/http" + + "github.com/JACKYMYPERSON/hldrCenter/config" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/logic/meeting" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/model" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/types" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/rest/httpx" +) + +func UpdateMeetingHandler(cfg *config.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UpdateMeetingReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + mysqlCfg := cfg.MySQL + dsn := fmt.Sprintf( + "%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true&loc=Local", + mysqlCfg.Username, + mysqlCfg.Password, + mysqlCfg.Host, + mysqlCfg.Port, + mysqlCfg.Database, + mysqlCfg.Charset, + ) + fmt.Println("接收到articlePost请求") + + conn := sqlx.NewSqlConn("mysql", dsn) + + meetingModel := model.NewMeetingModel(conn) + + l := meeting.NewUpdateMeetingLogic(r.Context(), cfg, meetingModel) + resp, err := l.UpdateMeeting(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/server/internal/meeting/internal/logic/meeting/createmeetinglogic.go b/server/internal/meeting/internal/logic/meeting/createmeetinglogic.go new file mode 100644 index 00000000..767525c4 --- /dev/null +++ b/server/internal/meeting/internal/logic/meeting/createmeetinglogic.go @@ -0,0 +1,87 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package meeting + +import ( + "context" + "database/sql" + "errors" + "time" + + "github.com/JACKYMYPERSON/hldrCenter/config" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/model" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CreateMeetingLogic struct { + logx.Logger + ctx context.Context + cfg *config.Config + model model.MeetingModel +} + +func NewCreateMeetingLogic(ctx context.Context, cfg *config.Config, model model.MeetingModel) *CreateMeetingLogic { + return &CreateMeetingLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + cfg: cfg, + model: model, + } +} + +func (l *CreateMeetingLogic) CreateMeeting(req *types.CreateMeetingReq) (resp *types.CreateMeetingResp, err error) { + // 1. 业务校验:验证时间格式及合理性 + // 1.1 解析开始时间(前端传入格式应为 "2006-01-02 15:04:05") + startTime, err := time.Parse("2006-01-02 15:04:05", req.StartTime) + if err != nil { + return nil, errors.New("开始时间格式错误,请使用YYYY-MM-DD HH:MM:SS") + } + + // 1.2 解析结束时间 + endTime, err := time.Parse("2006-01-02 15:04:05", req.EndTime) + if err != nil { + return nil, errors.New("结束时间格式错误,请使用YYYY-MM-DD HH:MM:SS") + } + + // 1.3 校验结束时间不能早于开始时间 + if endTime.Before(startTime) { + return nil, errors.New("结束时间不能早于开始时间") + } + + meeting := &model.Meeting{ + Theme: req.Theme, // 非空字段,直接赋值 + Subtitle: req.Subtitle, // 可空字段,空字符串会被数据库处理为 NULL(如果表允许) + Intro: sql.NullString{ + String: req.Intro, // 赋值实际字符串 + Valid: req.Intro != "", // 若不为空字符串,则设为有效(存实际值);若为空,则设为无效(存 NULL) + }, + CoverUrl: req.CoverUrl, + ScheduleImageUrl: req.ScheduleImageUrl, + StartTime: startTime, + EndTime: endTime, + IsDelete: 0, // 软删除默认未删除 + } + + // 3. 调用model插入数据库 + result, err := l.model.Insert(l.ctx, meeting) + if err != nil { + l.Logger.Errorf("创建会议失败:%v", err) + return nil, errors.New("创建会议失败,请重试") + } + + // 4. 获取新增会议的ID(通过LastInsertId()) + meetingId, err := result.LastInsertId() + if err != nil { + l.Logger.Errorf("获取会议ID失败:%v", err) + return nil, errors.New("创建会议成功,但获取ID失败") + } + + // 5. 构造响应 + return &types.CreateMeetingResp{ + Msg: "会议创建成功", + MeetingId: meetingId, // 返回新增会议的主键ID + }, nil +} diff --git a/server/internal/meeting/internal/logic/meeting/deletemeetinglogic.go b/server/internal/meeting/internal/logic/meeting/deletemeetinglogic.go new file mode 100644 index 00000000..088434e7 --- /dev/null +++ b/server/internal/meeting/internal/logic/meeting/deletemeetinglogic.go @@ -0,0 +1,80 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package meeting + +import ( + "context" + "errors" + + "github.com/JACKYMYPERSON/hldrCenter/config" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/model" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DeleteMeetingLogic struct { + logx.Logger + ctx context.Context + cfg *config.Config + model model.MeetingModel +} + +func NewDeleteMeetingLogic(ctx context.Context, cfg *config.Config, model model.MeetingModel) *DeleteMeetingLogic { + return &DeleteMeetingLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + cfg: cfg, + model: model, + } +} + +func (l *DeleteMeetingLogic) DeleteMeeting(req *types.DeleteMeetingReq) (resp *types.DeleteMeetingResp, err error) { + // 1. 校验会议ID有效性 + if req.Id <= 0 { + return nil, errors.New("会议ID无效,请传入正数ID") + } + + // 2. 检查会议是否存在(通过FindOne查询,处理返回值) + meeting, 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("删除失败,请重试") + } + // 若查询到会议(meeting不为空),继续执行删除 + if meeting == nil { + return nil, errors.New("会议不存在,无法删除") + } + + // 3. 执行软删除(更新 is_delete 字段为 1) + 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. 二次确认删除结果(查询 is_delete 是否为 1) + meeting, err = l.model.FindOne(l.ctx, req.Id) + if err != nil { + if err == model.ErrNotFound { + // 理论上软删除后记录仍存在,若返回 ErrNotFound 说明异常 + return nil, errors.New("删除状态异常,请检查数据") + } + l.Logger.Errorf("确认删除结果失败(ID: %d):%v", req.Id, err) + return nil, errors.New("删除状态确认失败,请稍后检查") + } + if meeting.IsDelete != 1 { + return nil, errors.New("删除未生效,请稍后重试") + } + + // 5. 返回删除成功响应 + return &types.DeleteMeetingResp{ + Msg: "会议已标记为删除", + }, nil +} diff --git a/server/internal/meeting/internal/logic/meeting/getmeetinglogic.go b/server/internal/meeting/internal/logic/meeting/getmeetinglogic.go new file mode 100644 index 00000000..9bfffff8 --- /dev/null +++ b/server/internal/meeting/internal/logic/meeting/getmeetinglogic.go @@ -0,0 +1,82 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package meeting + +import ( + "context" + "database/sql" + "errors" + + "github.com/JACKYMYPERSON/hldrCenter/config" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/model" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetMeetingLogic struct { + logx.Logger + ctx context.Context + cfg *config.Config + model model.MeetingModel +} + +func NewGetMeetingLogic(ctx context.Context, cfg *config.Config, model model.MeetingModel) *GetMeetingLogic { + return &GetMeetingLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + cfg: cfg, + model: model, + } +} + +func (l *GetMeetingLogic) GetMeeting(req *types.GetMeetingReq) (resp *types.GetMeetingResp, err error) { + // 1. 校验会议ID有效性 + if req.Id <= 0 { + return nil, errors.New("会议ID无效,请传入正数ID") + } + + // 2. 调用模型层查询会议详情 + meeting, 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. 转换模型数据为响应格式(处理时间字段和特殊类型) + meetingDetail := &types.MeetingDetail{ + Id: meeting.Id, + Theme: meeting.Theme, + Subtitle: meeting.Subtitle, + // 处理可能为 sql.NullString 类型的字段(如 Intro) + Intro: getNullStringValue(meeting.Intro), // 封装工具函数处理 + CoverUrl: meeting.CoverUrl, + ScheduleImageUrl: meeting.ScheduleImageUrl, + // 时间字段转换为字符串(格式:YYYY-MM-DD HH:MM:SS) + StartTime: meeting.StartTime.Format("2006-01-02 15:04:05"), + EndTime: meeting.EndTime.Format("2006-01-02 15:04:05"), + CreateTime: meeting.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: meeting.UpdateTime.Format("2006-01-02 15:04:05"), + Is_Deleted: meeting.IsDelete, // 返回软删除状态(可选,根据业务需求) + } + + // 4. 构造响应 + return &types.GetMeetingResp{ + Msg: "查询成功", + Meeting: meetingDetail, + }, nil +} + +// 工具函数:处理 sql.NullString 类型,返回字符串(空值时返回空字符串) +func getNullStringValue(nullStr sql.NullString) string { + if nullStr.Valid { + return nullStr.String + } + return "" +} diff --git a/server/internal/meeting/internal/logic/meeting/listmeetinglogic.go b/server/internal/meeting/internal/logic/meeting/listmeetinglogic.go new file mode 100644 index 00000000..bf5e8dc3 --- /dev/null +++ b/server/internal/meeting/internal/logic/meeting/listmeetinglogic.go @@ -0,0 +1,76 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package meeting + +import ( + "context" + "errors" + + "github.com/JACKYMYPERSON/hldrCenter/config" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/model" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ListMeetingLogic struct { + logx.Logger + ctx context.Context + cfg *config.Config + model model.MeetingModel +} + +func NewListMeetingLogic(ctx context.Context, cfg *config.Config, model model.MeetingModel) *ListMeetingLogic { + return &ListMeetingLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + cfg: cfg, + model: model, + } +} + +func (l *ListMeetingLogic) ListMeeting(req *types.ListMeetingReq) (resp *types.ListMeetingResp, err error) { + // 1. 处理分页参数(设置默认值和限制范围) + page := req.Page + if page <= 0 { + page = 1 // 默认第一页 + } + pageSize := req.PageSize + if pageSize <= 0 || pageSize > 100 { + pageSize = 10 // 默认10条/页,最大不超过100条 + } + + // 2. 调用模型层分页查询会议(只返回未删除的) + meetings, total, err := l.model.FindPage(l.ctx, page, pageSize) + if err != nil { + l.Logger.Errorf("分页查询会议列表失败(page: %d, pageSize: %d):%v", page, pageSize, err) + return nil, errors.New("查询会议列表失败,请重试") + } + + // 3. 转换模型数据为响应格式(批量处理) + var meetingDetails []*types.MeetingDetail + for _, meeting := range meetings { + meetingDetails = append(meetingDetails, &types.MeetingDetail{ + Id: meeting.Id, + Theme: meeting.Theme, + Subtitle: meeting.Subtitle, + // 处理 sql.NullString 类型(如 Intro) + Intro: getNullStringValue(meeting.Intro), + CoverUrl: meeting.CoverUrl, + ScheduleImageUrl: meeting.ScheduleImageUrl, + // 时间格式化 + StartTime: meeting.StartTime.Format("2006-01-02 15:04:05"), + EndTime: meeting.EndTime.Format("2006-01-02 15:04:05"), + CreateTime: meeting.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: meeting.UpdateTime.Format("2006-01-02 15:04:05"), + }) + } + + // 4. 构造响应(包含总条数和当前页数据) + return &types.ListMeetingResp{ + Msg: "查询成功", + Total: total, // 总条数(用于前端计算总页数) + Meetings: meetingDetails, // 当前页会议列表 + }, nil +} diff --git a/server/internal/meeting/internal/logic/meeting/updatemeetinglogic.go b/server/internal/meeting/internal/logic/meeting/updatemeetinglogic.go new file mode 100644 index 00000000..a8e97689 --- /dev/null +++ b/server/internal/meeting/internal/logic/meeting/updatemeetinglogic.go @@ -0,0 +1,114 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.9.2 + +package meeting + +import ( + "context" + "database/sql" + "errors" + "time" + + "github.com/JACKYMYPERSON/hldrCenter/config" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/model" + "github.com/JACKYMYPERSON/hldrCenter/internal/meeting/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UpdateMeetingLogic struct { + logx.Logger + ctx context.Context + cfg *config.Config + model model.MeetingModel +} + +func NewUpdateMeetingLogic(ctx context.Context, cfg *config.Config, model model.MeetingModel) *UpdateMeetingLogic { + return &UpdateMeetingLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + cfg: cfg, + model: model, + } +} + +func (l *UpdateMeetingLogic) UpdateMeeting(req *types.UpdateMeetingReq) (resp *types.UpdateMeetingResp, err error) { + // 1. 校验会议ID有效性 + if req.Id <= 0 { + return nil, errors.New("会议ID无效,请传入正数ID") + } + + // 2. 查询当前会议信息(确认会议存在,且获取原始数据用于补全未更新字段) + existingMeeting, 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. 处理时间字段(若请求提供了时间,则解析并校验;否则沿用原时间) + var startTime, endTime time.Time + var errTime error + + // 3.1 处理开始时间 + if req.StartTime != "" { + startTime, errTime = time.Parse("2006-01-02 15:04:05", req.StartTime) + if errTime != nil { + return nil, errors.New("开始时间格式错误,请使用YYYY-MM-DD HH:MM:SS") + } + } else { + startTime = existingMeeting.StartTime // 沿用原时间 + } + + // 3.2 处理结束时间 + if req.EndTime != "" { + endTime, errTime = time.Parse("2006-01-02 15:04:05", req.EndTime) + if errTime != nil { + return nil, errors.New("结束时间格式错误,请使用YYYY-MM-DD HH:MM:SS") + } + } else { + endTime = existingMeeting.EndTime // 沿用原时间 + } + + // 3.3 校验时间逻辑(若更新了时间,需确保结束时间不早于开始时间) + if endTime.Before(startTime) { + return nil, errors.New("结束时间不能早于开始时间") + } + + // 4. 构造更新数据(只更新请求中提供的非空字段,其他沿用原始值) + updateData := &model.Meeting{ + Id: req.Id, // 必须传入ID,用于WHERE条件定位记录 + Theme: req.Theme, + Subtitle: req.Subtitle, + // 处理Intro(若模型中是sql.NullString类型) + Intro: sql.NullString{String: req.Intro, Valid: req.Intro != ""}, + CoverUrl: req.CoverUrl, + ScheduleImageUrl: req.ScheduleImageUrl, + StartTime: startTime, + EndTime: endTime, + IsDelete: existingMeeting.IsDelete, // 软删除状态不允许通过此接口更新 + // CreateTime不更新(由数据库首次创建时生成) + // UpdateTime由数据库自动更新(表结构中已配置ON UPDATE CURRENT_TIMESTAMP) + } + + // 5. 特殊处理:若请求中某字段为空字符串,是否需要保留原始值? + // (根据业务需求调整,这里以“空字符串视为主动清空”为例,若需“不填则保留原值”需额外判断) + if req.Theme == "" { + updateData.Theme = existingMeeting.Theme // 若Theme为空,沿用原始值(避免清空必填字段) + } + // 其他字段(如Subtitle/CoverUrl等允许为空的字段,空字符串视为清空,无需特殊处理) + + // 6. 执行更新操作 + err = l.model.Update(l.ctx, updateData) + if err != nil { + l.Logger.Errorf("更新会议失败(ID: %d):%v", req.Id, err) + return nil, errors.New("更新失败,请重试") + } + + // 7. 构造响应 + return &types.UpdateMeetingResp{ + Msg: "会议更新成功", + }, nil +} diff --git a/server/internal/meeting/internal/model/meetingmodel.go b/server/internal/meeting/internal/model/meetingmodel.go new file mode 100644 index 00000000..c49602c2 --- /dev/null +++ b/server/internal/meeting/internal/model/meetingmodel.go @@ -0,0 +1,31 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ MeetingModel = (*customMeetingModel)(nil) + +type ( + // MeetingModel is an interface to be customized, add more methods here, + // and implement the added methods in customMeetingModel. + MeetingModel interface { + meetingModel + withSession(session sqlx.Session) MeetingModel + } + + customMeetingModel struct { + *defaultMeetingModel + } +) + +// NewMeetingModel returns a model for the database table. +func NewMeetingModel(conn sqlx.SqlConn) MeetingModel { + return &customMeetingModel{ + defaultMeetingModel: newMeetingModel(conn), + } +} + +func (m *customMeetingModel) withSession(session sqlx.Session) MeetingModel { + return NewMeetingModel(sqlx.NewSqlConnFromSession(session)) +} diff --git a/server/internal/meeting/internal/model/meetingmodel_gen.go b/server/internal/meeting/internal/model/meetingmodel_gen.go new file mode 100644 index 00000000..f99e8a51 --- /dev/null +++ b/server/internal/meeting/internal/model/meetingmodel_gen.go @@ -0,0 +1,123 @@ +// Code generated by goctl. DO NOT EDIT. +// versions: +// goctl version: 1.9.2 + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + "time" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + meetingFieldNames = builder.RawFieldNames(&Meeting{}) + meetingRows = strings.Join(meetingFieldNames, ",") + meetingRowsExpectAutoSet = strings.Join(stringx.Remove(meetingFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",") + meetingRowsWithPlaceHolder = strings.Join(stringx.Remove(meetingFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?" +) + +type ( + meetingModel interface { + Insert(ctx context.Context, data *Meeting) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*Meeting, error) + Update(ctx context.Context, data *Meeting) error + Delete(ctx context.Context, id int64) error + FindPage(ctx context.Context, page, pageSize int) ([]*Meeting, int64, error) + SoftDelete(ctx context.Context, id int64) error + } + + defaultMeetingModel struct { + conn sqlx.SqlConn + table string + } + + Meeting struct { + Id int64 `db:"id"` // 会议ID(主键) + Theme string `db:"theme"` // 会议主题 + Subtitle string `db:"subtitle"` // 会议副标题(允许为空) + Intro sql.NullString `db:"intro"` // 会议简介(支持长文本,可存Markdown) + CoverUrl string `db:"cover_url"` // 会议封面图片URL(允许为空) + ScheduleImageUrl string `db:"schedule_image_url"` // 会议日程图片URL(允许为空,无日程图时存空字符串) + StartTime time.Time `db:"start_time"` // 会议开始时间 + EndTime time.Time `db:"end_time"` // 会议结束时间 + CreateTime time.Time `db:"create_time"` // 创建时间 + UpdateTime time.Time `db:"update_time"` // 更新时间 + IsDelete int64 `db:"is_delete"` // 软删除(0-未删,1-已删) + } +) + +func newMeetingModel(conn sqlx.SqlConn) *defaultMeetingModel { + return &defaultMeetingModel{ + conn: conn, + table: "`meeting`", + } +} + +func (m *defaultMeetingModel) Delete(ctx context.Context, id int64) error { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +func (m *defaultMeetingModel) FindOne(ctx context.Context, id int64) (*Meeting, error) { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", meetingRows, m.table) + var resp Meeting + err := m.conn.QueryRowCtx(ctx, &resp, query, id) + switch err { + case nil: + return &resp, nil + case sqlx.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultMeetingModel) Insert(ctx context.Context, data *Meeting) (sql.Result, error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?)", m.table, meetingRowsExpectAutoSet) + ret, err := m.conn.ExecCtx(ctx, query, data.Theme, data.Subtitle, data.Intro, data.CoverUrl, data.ScheduleImageUrl, data.StartTime, data.EndTime, data.IsDelete) + return ret, err +} + +func (m *defaultMeetingModel) Update(ctx context.Context, data *Meeting) error { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, meetingRowsWithPlaceHolder) + _, err := m.conn.ExecCtx(ctx, query, data.Theme, data.Subtitle, data.Intro, data.CoverUrl, data.ScheduleImageUrl, data.StartTime, data.EndTime, data.IsDelete, data.Id) + return err +} + +func (m *defaultMeetingModel) FindPage(ctx context.Context, page, pageSize int) ([]*Meeting, int64, error) { + // 计算偏移量(分页公式:offset = (page-1) * pageSize) + offset := (page - 1) * pageSize + // 1. 查询当前页数据(过滤软删除,按创建时间倒序,最新的在前) + query := fmt.Sprintf("select %s from %s where is_delete = 0 order by create_time desc limit ?, ?", meetingRows, m.table) + var meetings []*Meeting + err := m.conn.QueryRowsCtx(ctx, &meetings, query, offset, pageSize) + if err != nil { + return nil, 0, err + } + // 2. 查询总条数(用于计算总页数) + countQuery := fmt.Sprintf("select count(1) from %s where is_delete = 0", m.table) + var total int64 + err = m.conn.QueryRowCtx(ctx, &total, countQuery) + if err != nil { + return nil, 0, err + } + return meetings, total, nil +} + +func (m *defaultMeetingModel) SoftDelete(ctx context.Context, id int64) error { + query := fmt.Sprintf("update %s set `is_delete` = 1 where `id` = ?", m.table) + _, err := m.conn.ExecCtx(ctx, query, id) + return err +} + +func (m *defaultMeetingModel) tableName() string { + return m.table +} diff --git a/server/internal/meeting/internal/model/vars.go b/server/internal/meeting/internal/model/vars.go new file mode 100644 index 00000000..69ca814e --- /dev/null +++ b/server/internal/meeting/internal/model/vars.go @@ -0,0 +1,5 @@ +package model + +import "github.com/zeromicro/go-zero/core/stores/sqlx" + +var ErrNotFound = sqlx.ErrNotFound diff --git a/server/internal/meeting/internal/types/types.go b/server/internal/meeting/internal/types/types.go new file mode 100644 index 00000000..9a6d0e11 --- /dev/null +++ b/server/internal/meeting/internal/types/types.go @@ -0,0 +1,76 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.9.2 + +package types + +type CreateMeetingReq struct { + Theme string `json:"theme" validate:"required"` // 会议主题(必填) + Subtitle string `json:"subtitle,omitempty"` // 副标题(可选) + Intro string `json:"intro,omitempty"` // 简介(可选) + CoverUrl string `json:"cover_url,omitempty"` // 封面图URL(可选) + ScheduleImageUrl string `json:"schedule_image_url,omitempty"` // 日程图URL(可选) + StartTime string `json:"start_time" validate:"required"` // 开始时间(必填,格式同上) + EndTime string `json:"end_time" validate:"required"` // 结束时间(必填) +} + +type CreateMeetingResp struct { + Msg string `json:"message"` // 操作提示(如:"创建成功") + MeetingId int64 `json:"meeting_id"` // 新增会议的ID(创建成功返回) +} + +type DeleteMeetingReq struct { + Id int64 `path:"id" validate:"required"` // 会议ID(从路径参数获取,必填) +} + +type DeleteMeetingResp struct { + Msg string `json:"message"` // 操作提示(如:"删除成功") +} + +type GetMeetingReq struct { + Id int64 `path:"id" validate:"required"` +} + +type GetMeetingResp struct { + Msg string `json:"message"` // 操作提示 + Meeting *MeetingDetail `json:"meeting"` // 会议详情(查询成功返回) +} + +type ListMeetingReq struct { + Page int `json:"page" query:"page" validate:"min=1"` // 页码(默认1) + PageSize int `json:"page_size" query:"page_size" validate:"min=1,max=100"` // 每页条数(默认10,最大100) +} + +type ListMeetingResp struct { + Msg string `json:"message"` // 操作提示 + Total int64 `json:"total"` // 总条数 + Meetings []*MeetingDetail `json:"meetings"` // 会议列表(当前页数据) +} + +type MeetingDetail struct { + Id int64 `json:"id"` // 会议ID(主键) + Theme string `json:"theme"` // 会议主题 + Subtitle string `json:"subtitle"` // 会议副标题 + Intro string `json:"intro"` // 会议简介(Markdown) + CoverUrl string `json:"cover_url"` // 封面图URL + ScheduleImageUrl string `json:"schedule_image_url"` // 日程图URL + StartTime string `json:"start_time"` // 开始时间(格式:YYYY-MM-DD HH:MM:SS) + EndTime string `json:"end_time"` // 结束时间(格式:YYYY-MM-DD HH:MM:SS) + CreateTime string `json:"create_time"` // 创建时间(数据库自动生成) + UpdateTime string `json:"update_time"` // 更新时间(数据库自动生成) + Is_Deleted int64 `json:"is_delete"` +} + +type UpdateMeetingReq struct { + Id int64 `json:"id" validate:"required"` // 会议ID(必填,用于定位要更新的记录) + Theme string `json:"theme,omitempty"` // 会议主题(可选,不填则不更新) + Subtitle string `json:"subtitle,omitempty"` // 副标题(可选) + Intro string `json:"intro,omitempty"` // 简介(可选) + CoverUrl string `json:"cover_url,omitempty"` // 封面图URL(可选) + ScheduleImageUrl string `json:"schedule_image_url,omitempty"` // 日程图URL(可选) + StartTime string `json:"start_time,omitempty"` // 开始时间(可选) + EndTime string `json:"end_time,omitempty"` // 结束时间(可选) +} + +type UpdateMeetingResp struct { + Msg string `json:"message"` // 操作提示(如:"更新成功") +}