Commits (2)
package route package route
import ( import (
"go-svc-tpl/api/dto"
"go-svc-tpl/internal/controller" "go-svc-tpl/internal/controller"
"github.com/gin-gonic/gin"
) )
// 设置路由
func setupLinkController(r *gin.RouterGroup) {
lcw := LinkCtlWrapper{
ctl: controller.NewLinkController(), //Factory method of ILinkController.
// Implemented in controller package.
}
p := r.Group("/link")
p.POST("/create", controller.ParseTokenMidware(), lcw.Create)
p.POST("/delete", controller.ParseTokenMidware(), lcw.Delete)
p.GET("/getinfo", controller.ParseTokenMidware(), lcw.GetInfo)
p.POST("/update", controller.ParseTokenMidware(), lcw.Update)
p.GET("/getlist", controller.ParseTokenMidware(), lcw.GetList)
}
type LinkCtlWrapper struct { type LinkCtlWrapper struct {
ctl controller.ILinkController ctl controller.ILinkController
} }
// create link
func (w *LinkCtlWrapper) Create(c *gin.Context) {
var req dto.LinkCreateReq
if err := dto.BindReq(c, &req); err != nil {
dto.ResponseFail(c, err)
return
}
resp, err := w.ctl.Create(c, &req)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, resp)
}
// delete link
func (w *LinkCtlWrapper) Delete(c *gin.Context) {
var req dto.LinkDeleteReq
if err := dto.BindReq(c, &req); err != nil {
dto.ResponseFail(c, err)
return
}
err := w.ctl.Delete(c, &req)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, "delete successfully")
}
// get information
func (w *LinkCtlWrapper) GetInfo(c *gin.Context) {
var req dto.GetLinkInfoReq
if err := dto.BindReq(c, &req); err != nil {
dto.ResponseFail(c, err)
return
}
resp, err := w.ctl.GetInfo(c, &req)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, resp)
}
// update link
func (w *LinkCtlWrapper) Update(c *gin.Context) {
var req dto.LinkUpdateReq
if err := dto.BindReq(c, &req); err != nil {
dto.ResponseFail(c, err)
return
}
err := w.ctl.Update(c, &req)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, "update successfully")
}
// get list
func (w *LinkCtlWrapper) GetList(c *gin.Context) {
var req dto.GetLinkListReq
if err := dto.BindReq(c, &req); err != nil {
dto.ResponseFail(c, err)
return
}
resp, err := w.ctl.GetList(c, &req)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, resp)
}
package route package route
import ( import (
"go-svc-tpl/api/dto"
"go-svc-tpl/internal/controller" "go-svc-tpl/internal/controller"
"github.com/gin-gonic/gin"
) )
// 设置路由
func setupServerController(r *gin.RouterGroup) {
lcw := ServerCtlWrapper{
ctl: controller.NewServerController(), //Factory method of ILinkController.
// Implemented in controller package.
}
p := r.Group("/server")
p.GET("/getlink", controller.ParseTokenMidware(), lcw.Link)
p.GET("/getveri", controller.ParseTokenMidware(), lcw.Veri)
}
type ServerCtlWrapper struct { //Wrapper类隔离接口具体逻辑 type ServerCtlWrapper struct { //Wrapper类隔离接口具体逻辑
ctl controller.IServerController ctl controller.IServerController
} }
// get link
func (w *ServerCtlWrapper) Link(c *gin.Context) {
var req dto.ServerLinkReq
if err := dto.BindReq(c, &req); err != nil {
dto.ResponseFail(c, err)
return
}
err := w.ctl.Link(c, &req)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, "get link server successfully")
}
// get verification
func (w *ServerCtlWrapper) Veri(c *gin.Context) {
var req dto.ServerVeriReq
if err := dto.BindReq(c, &req); err != nil {
dto.ResponseFail(c, err)
return
}
err := w.ctl.Veri(c, &req)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, "get verification server successfully")
}
package route package route
import ( import (
"go-svc-tpl/api/dto"
"go-svc-tpl/internal/controller" "go-svc-tpl/internal/controller"
"github.com/gin-gonic/gin"
) )
// 设置路由
func setupUserController(r *gin.RouterGroup) {
lcw := UserCtlWrapper{
ctl: controller.NewUserController(), //Factory method of ILinkController.
// Implemented in controller package.
}
p := r.Group("/user")
p.POST("/register", controller.ParseTokenMidware(), lcw.Register)
p.GET("/getveri", controller.ParseTokenMidware(), lcw.GetVeri)
p.POST("/login", controller.ParseTokenMidware(), lcw.Login)
p.GET("/getinfo", controller.ParseTokenMidware(), lcw.GetInfo)
p.POST("/modifyinfo", controller.ParseTokenMidware(), lcw.ModifyInfo)
p.POST("/modifypwd", controller.ParseTokenMidware(), lcw.ModifyPwd)
}
type UserCtlWrapper struct { //Wrapper类隔离接口具体逻辑 type UserCtlWrapper struct { //Wrapper类隔离接口具体逻辑
ctl controller.IUserController ctl controller.IUserController
} }
// register user
func (w *UserCtlWrapper) Register(c *gin.Context) {
var req dto.UserRegisterReq
if err := dto.BindReq(c, &req); err != nil {
dto.ResponseFail(c, err)
return
}
err := w.ctl.Register(c, &req)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, "register successfully")
}
// get verification
func (w *UserCtlWrapper) GetVeri(c *gin.Context) {
//var req dto.UserRegisterReq
//if err := dto.BindReq(c, &req); err != nil {
// dto.ResponseFail(c, err)
// return
//}
resp, err := w.ctl.GetVeri(c)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, resp)
}
// login
func (w *UserCtlWrapper) Login(c *gin.Context) {
var req dto.UserLoginReq
if err := dto.BindReq(c, &req); err != nil {
dto.ResponseFail(c, err)
return
}
err := w.ctl.Login(c, &req)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, "login successfully")
}
// get user information
func (w *UserCtlWrapper) GetInfo(c *gin.Context) {
//var req dto.UserRegisterReq
//if err := dto.BindReq(c, &req); err != nil {
// dto.ResponseFail(c, err)
// return
//}
resp, err := w.ctl.GetInfo(c)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, resp)
}
// modify user information
func (w *UserCtlWrapper) ModifyInfo(c *gin.Context) {
var req dto.UserModifyInfoReq
if err := dto.BindReq(c, &req); err != nil {
dto.ResponseFail(c, err)
return
}
err := w.ctl.ModifyInfo(c, &req)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, "modify information successfully")
}
// modify user password
func (w *UserCtlWrapper) ModifyPwd(c *gin.Context) {
var req dto.UserModifyPwdReq
if err := dto.BindReq(c, &req); err != nil {
dto.ResponseFail(c, err)
return
}
err := w.ctl.ModifyPwd(c, &req)
if err != nil {
dto.ResponseFail(c, err)
return
}
dto.ResponseSuccess(c, "modify password successfully")
}
...@@ -18,6 +18,7 @@ require ( ...@@ -18,6 +18,7 @@ require (
github.com/bytedance/sonic v1.9.1 // indirect github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
......
...@@ -63,6 +63,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t ...@@ -63,6 +63,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
......
package controller package controller
import ( import (
"fmt"
"go-svc-tpl/api/dto" "go-svc-tpl/api/dto"
"go-svc-tpl/internal/dao"
"go-svc-tpl/internal/dao/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm"
) )
const USER_ID_KEY = "qwq"
const LinkTable = "Links"
// >>>>>>>>>>>>>>>>>> Interface >>>>>>>>>>>>>>>>>> // >>>>>>>>>>>>>>>>>> Interface >>>>>>>>>>>>>>>>>>
type ILinkController interface { //定义tag link 下的操作 type ILinkController interface { //定义tag link 下的操作
Create(*gin.Context, *dto.LinkCreateReq) (*dto.LinkCreateResp, error) Create(*gin.Context, *dto.LinkCreateReq) (*dto.LinkCreateResp, error)
...@@ -14,3 +21,155 @@ type ILinkController interface { //定义tag link 下的操作 ...@@ -14,3 +21,155 @@ type ILinkController interface { //定义tag link 下的操作
Update(*gin.Context, *dto.LinkUpdateReq) error Update(*gin.Context, *dto.LinkUpdateReq) error
GetList(*gin.Context, *dto.GetLinkListReq) (*dto.GetLinkListResp, error) GetList(*gin.Context, *dto.GetLinkListReq) (*dto.GetLinkListResp, error)
} }
// >>>>>>>>>>>>>>>>>> Controller >>>>>>>>>>>>>>>>>>
// check interface implementation
var _ ILinkController = (*LinkController)(nil)
var NewLinkController = func() *LinkController {
return &LinkController{}
}
type LinkController struct {
db *gorm.DB
}
// create
// 用gorm创建短链接
// short 如果为空字符串,则自动生成链接
// 如果起止时间为 null 则表示不设置起止时间
func (c *LinkController) Create(ctx *gin.Context, req *dto.LinkCreateReq) (*dto.LinkCreateResp, error) {
userID := ctx.GetUint(USER_ID_KEY)
newLink := &model.Link{
Short: req.Short,
Comment: req.Comment,
Origin: req.Origin,
StartTime: req.StartTime,
EndTime: req.EndTime,
Active: true,
OwnerID: userID,
}
err := dao.DB(ctx).Create(newLink).Error
if err != nil {
return nil, err
}
var link model.Link
err = dao.DB(ctx).First(&link, "Short = ?", newLink.Short).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
fmt.Println("Short is not duplicate. You can insert.")
}
return nil, err
}
return &dto.LinkCreateResp{
Short: newLink.Short,
Origin: newLink.Origin,
Comment: newLink.Comment,
StartTime: newLink.StartTime,
EndTime: newLink.EndTime,
Active: newLink.Active,
}, nil
}
// delete
func (c *LinkController) Delete(ctx *gin.Context, req *dto.LinkDeleteReq) error {
userID := ctx.GetUint(USER_ID_KEY)
deleteLink := &model.Link{
Short: req.Short,
OwnerID: userID,
}
err := dao.DB(ctx).Delete(&deleteLink).Error
if err != nil {
return err
}
var link model.Link
err = dao.DB(ctx).First(&link, "Short = ?", deleteLink.Short).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
fmt.Println("Short is not duplicate. You can insert.")
}
return err
}
return nil
}
// getinfo
func (c *LinkController) GetInfo(ctx *gin.Context, req *dto.GetLinkInfoReq) (*dto.GetLinkInfoResp, error) {
userID := ctx.GetUint(USER_ID_KEY)
getinfoLink := &model.Link{
Short: req.Short,
OwnerID: userID,
}
var link model.Link
err := dao.DB(ctx).First(&link, "Short = ?", getinfoLink.Short).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
fmt.Println("Short is not duplicate. You can insert.")
}
return nil, err
}
return &dto.GetLinkInfoResp{
Short: link.Short,
Origin: link.Origin,
Comment: link.Comment,
StartTime: link.StartTime,
EndTime: link.EndTime,
Active: link.Active,
}, nil
}
// update
func (c *LinkController) Update(ctx *gin.Context, req *dto.LinkUpdateReq) error {
userID := ctx.GetUint(USER_ID_KEY)
updateLink := &model.Link{
Short: req.Short,
Origin: req.Origin,
Comment: req.Comment,
StartTime: req.StartTime,
EndTime: req.EndTime,
Active: req.Active,
OwnerID: userID,
}
err := dao.DB(ctx).Updates(&updateLink).Error
if err != nil {
return err
}
var link model.Link
err = dao.DB(ctx).First(&link, "Short = ?", updateLink.Short).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
fmt.Println("Short is not duplicate. You can insert.")
}
return err
}
return nil
}
// getlist
func (c *LinkController) GetList(ctx *gin.Context, req *dto.GetLinkListReq) (*dto.GetLinkListResp, error) {
var links []dto.ShortLinkModel
var total int64
query := dao.DB(ctx).Model(&dto.ShortLinkModel{})
if req.PageNumber > 0 && req.PageSize > 0 {
offset := (req.PageNumber - 1) * req.PageSize
query = query.Offset(int(offset)).Limit(int(req.PageSize))
}
if result := query.Find(&links); result.Error != nil {
return nil, result.Error
}
query.Count(&total)
resp := &dto.GetLinkListResp{
Links: links,
Total: total,
}
return resp, nil
}
package controller
import (
"net/http"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
)
// 解析和验证身份认证令牌
func ParseTokenMidware() gin.HandlerFunc {
return func(c *gin.Context) {
// 在此处添加解析和验证令牌的逻辑
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Missing token"})
return
}
// 解析和验证令牌
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// 在此处添加密钥获取逻辑
// 通常从配置文件、环境变量等获取密钥
return []byte("your-secret-key"), nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
return
}
// 令牌验证通过,继续处理请求
c.Next()
}
}
...@@ -2,8 +2,11 @@ package controller ...@@ -2,8 +2,11 @@ package controller
import ( import (
"go-svc-tpl/api/dto" "go-svc-tpl/api/dto"
"go-svc-tpl/internal/dao/model"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm"
) )
// >>>>>>>>>>>>>>>>>> Interface >>>>>>>>>>>>>>>>>> // >>>>>>>>>>>>>>>>>> Interface >>>>>>>>>>>>>>>>>>
...@@ -11,3 +14,42 @@ type IServerController interface { ...@@ -11,3 +14,42 @@ type IServerController interface {
Link(*gin.Context, *dto.ServerLinkReq) error Link(*gin.Context, *dto.ServerLinkReq) error
Veri(*gin.Context, *dto.ServerVeriReq) error Veri(*gin.Context, *dto.ServerVeriReq) error
} }
// >>>>>>>>>>>>>>>>>> Controller >>>>>>>>>>>>>>>>>
var _ IServerController = (*ServerController)(nil)
var NewServerController = func() *ServerController {
return &ServerController{}
}
type ServerController struct {
db *gorm.DB
}
// 重定向到相应链接
// 当出现问题时,会按标准 response 返回,由前端显示相应页面
func (c *ServerController) Link(ctx *gin.Context, req *dto.ServerLinkReq) error {
shortLink := req.Short
// 在数据库中查找对应的长链接
var link model.Link
if err := c.db.Table(model.LinkTable).Where("short = ?", shortLink).First(&link).Error; err != nil {
if err == gorm.ErrRecordNotFound {
// 如果找不到对应的长链接,返回404错误
ctx.JSON(http.StatusNotFound, gin.H{"error": "Link not found"})
} else {
// 如果发生其他错误,返回500错误
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
}
return err
}
// 如果找到了对应的长链接,进行重定向
ctx.Redirect(http.StatusMovedPermanently, link.Origin)
return nil
}
func (c *ServerController) Veri(ctx *gin.Context, req *dto.ServerVeriReq) error {
//no
return nil
}
package controller package controller
import ( import (
"fmt"
"go-svc-tpl/api/dto" "go-svc-tpl/api/dto"
"go-svc-tpl/internal/dao"
"go-svc-tpl/internal/dao/model"
"regexp"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm"
) )
// >>>>>>>>>>>>>>>>>> Interface >>>>>>>>>>>>>>>>>> // >>>>>>>>>>>>>>>>>> Interface >>>>>>>>>>>>>>>>>>
...@@ -15,3 +20,116 @@ type IUserController interface { //定义tag user 下的操作 ...@@ -15,3 +20,116 @@ type IUserController interface { //定义tag user 下的操作
ModifyInfo(*gin.Context, *dto.UserModifyInfoReq) error ModifyInfo(*gin.Context, *dto.UserModifyInfoReq) error
ModifyPwd(*gin.Context, *dto.UserModifyPwdReq) error ModifyPwd(*gin.Context, *dto.UserModifyPwdReq) error
} }
var _ IUserController = (*UserController)(nil)
var NewUserController = func() *UserController {
return &UserController{}
}
type UserController struct {
db *gorm.DB
}
// Register
func (c *UserController) Register(ctx *gin.Context, req *dto.UserRegisterReq) error {
userID := ctx.GetUint(USER_ID_KEY)
//验证邮箱格式
emailPattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$`
_, err := regexp.MatchString(emailPattern, req.Email)
if err != nil {
fmt.Println("wrong email pattern", err)
return err
}
newUser := &model.User{
ID: userID,
Email: req.Email,
Name: req.Name,
Password: req.Password,
}
err = dao.DB(ctx).Create(newUser).Error
if err != nil {
return err
}
//验证邮箱唯一性
var user model.User
err = dao.DB(ctx).First(&user, "Email = ?", newUser.Email).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
fmt.Println("Email is not duplicate. You can insert.")
}
return err
}
return nil
}
// GetVeri
func (c *UserController) GetVeri(ctx *gin.Context) (*dto.GetVeriResp, error) {
//no
return nil, nil
}
// Login
func (c *UserController) Login(ctx *gin.Context, req *dto.UserLoginReq) error {
//no
return nil
}
// GetInfo
func (c *UserController) GetInfo(ctx *gin.Context) (*dto.GetUserInfoResp, error) {
userID := ctx.GetUint(USER_ID_KEY)
var user model.User
err := dao.DB(ctx).First(&user, "ID = ?", userID).Error
if err != nil {
return nil, err
}
return &dto.GetUserInfoResp{
ID: user.ID,
Name: user.Name,
Email: user.Email,
}, nil
}
// ModifyInfo
func (c *UserController) ModifyInfo(ctx *gin.Context, req *dto.UserModifyInfoReq) error {
userID := ctx.GetUint(USER_ID_KEY)
//验证邮箱格式
emailPattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$`
_, err := regexp.MatchString(emailPattern, req.Email)
if err != nil {
fmt.Println("wrong email pattern", err)
return err
}
newUser := &model.User{
ID: userID,
Email: req.Email,
Name: req.Name,
}
err = dao.DB(ctx).Updates(&newUser).Error
if err != nil {
return err
}
return nil
}
// ModifyPwd
func (c *UserController) ModifyPwd(ctx *gin.Context, req *dto.UserModifyPwdReq) error {
userID := ctx.GetUint(USER_ID_KEY)
var user model.User
err := dao.DB(ctx).First(&user, "ID = ?", userID).Error
if err != nil {
return err
}
if user.Password != req.OldPwd {
fmt.Println("wrong password")
}
newUser := &model.User{
Password: req.NewPwd,
}
err = dao.DB(ctx).Updates(&newUser).Error
if err != nil {
return err
}
return nil
}
package model package model
import "time" import (
"gopkg.in/guregu/null.v4"
)
const LinkTable = "Links" const LinkTable = "Links"
type Link struct { type Link struct {
ID uint `json:"id"` // 链接ID ID uint `json:"ID"` //ID
Title string `json:"title"` // 链接标题 Active bool `json:"active"`
URL string `json:"url"` // 链接URL Comment string `json:"comment"`
OwnerID uint `json:"owner_id"` // 拥有者ID EndTime null.Time `json:"end_time"`
CreatedAt time.Time `json:"created_at"` // 创建时间 Origin string `json:"origin"`
UpdatedAt time.Time `json:"updated_at"` // 更新时间 Short string `json:"short"`
StartTime null.Time `json:"start_time"`
OwnerID uint `json:"owner_id"`
} }