This commit is contained in:
kenneth
2023-12-01 02:46:50 +00:00
parent 90e6ba5070
commit 1bb57bc94a
26 changed files with 1312 additions and 169 deletions

View File

@@ -15,3 +15,19 @@ type User struct {
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
type Video struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Images string `json:"images"`
OriginLink string `json:"origin_link"`
PlayLink string `json:"play_link"`
Status int32 `json:"status"`
IsDeleted bool `json:"is_deleted"`
UserID string `json:"user_id"`
CreateAt time.Time `json:"create_at"`
CreateBy string `json:"create_by"`
UpdateAt time.Time `json:"update_at"`
UpdateBy string `json:"update_by"`
}

View File

@@ -10,12 +10,20 @@ import (
type Querier interface {
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
CreateVideo(ctx context.Context, arg CreateVideoParams) (Video, error)
DeleteUser(ctx context.Context, id string) error
DeleteVideo(ctx context.Context, id string) error
GetUser(ctx context.Context, id string) (User, error)
GetUserByEmail(ctx context.Context, email string) (User, error)
GetUserByName(ctx context.Context, username string) (User, error)
GetVideo(ctx context.Context, id string) (Video, error)
ListUsers(ctx context.Context, arg ListUsersParams) ([]User, error)
ListVideos(ctx context.Context, arg ListVideosParams) ([]Video, error)
ListVideosByUser(ctx context.Context, arg ListVideosByUserParams) ([]Video, error)
SetVideoPlay(ctx context.Context, arg SetVideoPlayParams) (Video, error)
UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error)
UpdateVideo(ctx context.Context, arg UpdateVideoParams) (Video, error)
UpdateVideoStatus(ctx context.Context, arg UpdateVideoStatusParams) (Video, error)
}
var _ Querier = (*Queries)(nil)

View File

@@ -0,0 +1,58 @@
-- name: CreateVideo :one
INSERT INTO videos (
id, title, description, images, origin_link, play_link, user_id, create_by
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8
)
RETURNING *;
-- name: DeleteVideo :exec
UPDATE videos
SET is_deleted = TRUE
WHERE id = $1;
-- name: UpdateVideoStatus :one
UPDATE videos
SET status = $2,
update_at = $3,
update_by = $4
WHERE id = $1
RETURNING *;
-- name: SetVideoPlay :one
UPDATE videos
SET status = $2,
play_link = $3,
update_at = $4,
update_by = $5
WHERE id = $1
RETURNING *;
-- name: UpdateVideo :one
UPDATE videos
SET title = $2,
description = $3,
images = $4,
status = $5,
update_at = $6,
update_by = $7
WHERE id = $1
RETURNING *;
-- name: GetVideo :one
SELECT * FROM videos
WHERE id = $1 LIMIT 1;
-- name: ListVideos :many
SELECT * FROM videos
WHERE is_deleted = FALSE AND status=200
ORDER BY id DESC
LIMIT $1
OFFSET $2;
-- name: ListVideosByUser :many
SELECT * FROM videos
WHERE is_deleted = FALSE AND user_id = $1
ORDER BY id DESC
LIMIT $2
OFFSET $3;

View File

@@ -1 +1,2 @@
DROP TABLE "videos";
DROP TABLE "users";

View File

@@ -1,7 +1,39 @@
CREATE TABLE "users" (
"id" varchar NOT NULL PRIMARY KEY,
"username" varchar NOT NULL UNIQUE,
"username" varchar NOT NULL,
"hashed_password" varchar NOT NULL,
"email" varchar NOT NULL UNIQUE,
"email" varchar NOT NULL,
"created_at" timestamptz NOT NULL DEFAULT (now())
);
);
ALTER TABLE "users" ADD CONSTRAINT "username_key" UNIQUE ("username");
ALTER TABLE "users" ADD CONSTRAINT "email_key" UNIQUE ("email");
CREATE INDEX ON "users" ("username");
CREATE INDEX ON "users" ("email");
CREATE TABLE "videos" (
"id" varchar NOT NULL PRIMARY KEY,
"title" varchar NOT NULL,
"description" varchar NOT NULL,
"images" varchar NOT NULL,
"origin_link" varchar NOT NULL,
"play_link" varchar NOT NULL,
-- 100: 下架
-- 0: 添加视频
-- 1: 视频转码中
-- 2: 视频转码失败
-- 200: 视频正常显示播放
"status" int NOT NULL DEFAULT (0),
"is_deleted" boolean NOT NULL DEFAULT false, -- 删除
"user_id" varchar NOT NULL,
"create_at" timestamptz NOT NULL DEFAULT (now()),
"create_by" varchar NOT NULL,
"update_at" timestamptz NOT NULL DEFAULT('0001-01-01 00:00:00Z'),
"update_by" varchar NOT NULL DEFAULT ('')
);
ALTER TABLE "videos" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id");
CREATE INDEX ON "videos" ("status");
CREATE INDEX ON "videos" ("user_id", "status", "create_at");
CREATE INDEX ON "videos" ("title");
CREATE INDEX ON "videos" ("user_id", "title");

337
internal/db/video.sql.go Normal file
View File

@@ -0,0 +1,337 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.24.0
// source: video.sql
package db
import (
"context"
"time"
)
const createVideo = `-- name: CreateVideo :one
INSERT INTO videos (
id, title, description, images, origin_link, play_link, user_id, create_by
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8
)
RETURNING id, title, description, images, origin_link, play_link, status, is_deleted, user_id, create_at, create_by, update_at, update_by
`
type CreateVideoParams struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Images string `json:"images"`
OriginLink string `json:"origin_link"`
PlayLink string `json:"play_link"`
UserID string `json:"user_id"`
CreateBy string `json:"create_by"`
}
func (q *Queries) CreateVideo(ctx context.Context, arg CreateVideoParams) (Video, error) {
row := q.db.QueryRowContext(ctx, createVideo,
arg.ID,
arg.Title,
arg.Description,
arg.Images,
arg.OriginLink,
arg.PlayLink,
arg.UserID,
arg.CreateBy,
)
var i Video
err := row.Scan(
&i.ID,
&i.Title,
&i.Description,
&i.Images,
&i.OriginLink,
&i.PlayLink,
&i.Status,
&i.IsDeleted,
&i.UserID,
&i.CreateAt,
&i.CreateBy,
&i.UpdateAt,
&i.UpdateBy,
)
return i, err
}
const deleteVideo = `-- name: DeleteVideo :exec
UPDATE videos
SET is_deleted = TRUE
WHERE id = $1
`
func (q *Queries) DeleteVideo(ctx context.Context, id string) error {
_, err := q.db.ExecContext(ctx, deleteVideo, id)
return err
}
const getVideo = `-- name: GetVideo :one
SELECT id, title, description, images, origin_link, play_link, status, is_deleted, user_id, create_at, create_by, update_at, update_by FROM videos
WHERE id = $1 LIMIT 1
`
func (q *Queries) GetVideo(ctx context.Context, id string) (Video, error) {
row := q.db.QueryRowContext(ctx, getVideo, id)
var i Video
err := row.Scan(
&i.ID,
&i.Title,
&i.Description,
&i.Images,
&i.OriginLink,
&i.PlayLink,
&i.Status,
&i.IsDeleted,
&i.UserID,
&i.CreateAt,
&i.CreateBy,
&i.UpdateAt,
&i.UpdateBy,
)
return i, err
}
const listVideos = `-- name: ListVideos :many
SELECT id, title, description, images, origin_link, play_link, status, is_deleted, user_id, create_at, create_by, update_at, update_by FROM videos
WHERE is_deleted = FALSE AND status=200
ORDER BY id DESC
LIMIT $1
OFFSET $2
`
type ListVideosParams struct {
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListVideos(ctx context.Context, arg ListVideosParams) ([]Video, error) {
rows, err := q.db.QueryContext(ctx, listVideos, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Video{}
for rows.Next() {
var i Video
if err := rows.Scan(
&i.ID,
&i.Title,
&i.Description,
&i.Images,
&i.OriginLink,
&i.PlayLink,
&i.Status,
&i.IsDeleted,
&i.UserID,
&i.CreateAt,
&i.CreateBy,
&i.UpdateAt,
&i.UpdateBy,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listVideosByUser = `-- name: ListVideosByUser :many
SELECT id, title, description, images, origin_link, play_link, status, is_deleted, user_id, create_at, create_by, update_at, update_by FROM videos
WHERE is_deleted = FALSE AND user_id = $1
ORDER BY id DESC
LIMIT $2
OFFSET $3
`
type ListVideosByUserParams struct {
UserID string `json:"user_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListVideosByUser(ctx context.Context, arg ListVideosByUserParams) ([]Video, error) {
rows, err := q.db.QueryContext(ctx, listVideosByUser, arg.UserID, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Video{}
for rows.Next() {
var i Video
if err := rows.Scan(
&i.ID,
&i.Title,
&i.Description,
&i.Images,
&i.OriginLink,
&i.PlayLink,
&i.Status,
&i.IsDeleted,
&i.UserID,
&i.CreateAt,
&i.CreateBy,
&i.UpdateAt,
&i.UpdateBy,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const setVideoPlay = `-- name: SetVideoPlay :one
UPDATE videos
SET status = $2,
play_link = $3,
update_at = $4,
update_by = $5
WHERE id = $1
RETURNING id, title, description, images, origin_link, play_link, status, is_deleted, user_id, create_at, create_by, update_at, update_by
`
type SetVideoPlayParams struct {
ID string `json:"id"`
Status int32 `json:"status"`
PlayLink string `json:"play_link"`
UpdateAt time.Time `json:"update_at"`
UpdateBy string `json:"update_by"`
}
func (q *Queries) SetVideoPlay(ctx context.Context, arg SetVideoPlayParams) (Video, error) {
row := q.db.QueryRowContext(ctx, setVideoPlay,
arg.ID,
arg.Status,
arg.PlayLink,
arg.UpdateAt,
arg.UpdateBy,
)
var i Video
err := row.Scan(
&i.ID,
&i.Title,
&i.Description,
&i.Images,
&i.OriginLink,
&i.PlayLink,
&i.Status,
&i.IsDeleted,
&i.UserID,
&i.CreateAt,
&i.CreateBy,
&i.UpdateAt,
&i.UpdateBy,
)
return i, err
}
const updateVideo = `-- name: UpdateVideo :one
UPDATE videos
SET title = $2,
description = $3,
images = $4,
status = $5,
update_at = $6,
update_by = $7
WHERE id = $1
RETURNING id, title, description, images, origin_link, play_link, status, is_deleted, user_id, create_at, create_by, update_at, update_by
`
type UpdateVideoParams struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Images string `json:"images"`
Status int32 `json:"status"`
UpdateAt time.Time `json:"update_at"`
UpdateBy string `json:"update_by"`
}
func (q *Queries) UpdateVideo(ctx context.Context, arg UpdateVideoParams) (Video, error) {
row := q.db.QueryRowContext(ctx, updateVideo,
arg.ID,
arg.Title,
arg.Description,
arg.Images,
arg.Status,
arg.UpdateAt,
arg.UpdateBy,
)
var i Video
err := row.Scan(
&i.ID,
&i.Title,
&i.Description,
&i.Images,
&i.OriginLink,
&i.PlayLink,
&i.Status,
&i.IsDeleted,
&i.UserID,
&i.CreateAt,
&i.CreateBy,
&i.UpdateAt,
&i.UpdateBy,
)
return i, err
}
const updateVideoStatus = `-- name: UpdateVideoStatus :one
UPDATE videos
SET status = $2,
update_at = $3,
update_by = $4
WHERE id = $1
RETURNING id, title, description, images, origin_link, play_link, status, is_deleted, user_id, create_at, create_by, update_at, update_by
`
type UpdateVideoStatusParams struct {
ID string `json:"id"`
Status int32 `json:"status"`
UpdateAt time.Time `json:"update_at"`
UpdateBy string `json:"update_by"`
}
func (q *Queries) UpdateVideoStatus(ctx context.Context, arg UpdateVideoStatusParams) (Video, error) {
row := q.db.QueryRowContext(ctx, updateVideoStatus,
arg.ID,
arg.Status,
arg.UpdateAt,
arg.UpdateBy,
)
var i Video
err := row.Scan(
&i.ID,
&i.Title,
&i.Description,
&i.Images,
&i.OriginLink,
&i.PlayLink,
&i.Status,
&i.IsDeleted,
&i.UserID,
&i.CreateAt,
&i.CreateBy,
&i.UpdateAt,
&i.UpdateBy,
)
return i, err
}

View File

@@ -14,9 +14,9 @@ const (
type CtxTypeUser string
type authorize struct {
AuthID string `json:"auth_id"`
AuthName string `json:"auth_name"`
type Authorize struct {
ID string `json:"id"`
Name string `json:"name"`
}
func genId() string {

View File

@@ -1,20 +1,39 @@
package handlers
import (
"log"
"net/http"
"strings"
"github.com/zhang2092/mediahls/internal/db"
)
type pageData struct {
AuthID string
AuthName string
Authorize
Videos []db.Video
}
func (server *Server) home(w http.ResponseWriter, r *http.Request) {
pd := pageData{}
auth, err := server.withCookie(r)
if err == nil {
pd.AuthID = auth.AuthID
pd.AuthName = auth.AuthName
pd.Authorize = *auth
}
ctx := r.Context()
videos, err := server.store.ListVideos(ctx, db.ListVideosParams{
Limit: 100,
Offset: 0,
})
if err == nil {
for _, item := range videos {
if len(item.Description) > 65 {
temp := strings.TrimSpace(item.Description[0:65]) + "..."
item.Description = temp
log.Println(item.Description)
}
pd.Videos = append(pd.Videos, item)
}
}
renderHome(w, pd)
}

View File

@@ -30,14 +30,14 @@ func (server *Server) authorizeMiddleware(next http.Handler) http.Handler {
})
}
func (server *Server) withCookie(r *http.Request) (*authorize, error) {
func (server *Server) withCookie(r *http.Request) (*Authorize, error) {
cookie, err := r.Cookie(AuthorizeCookie)
if err != nil {
log.Printf("get cookie: %v", err)
return nil, err
}
u := &authorize{}
u := &Authorize{}
err = server.secureCookie.Decode(AuthorizeCookie, cookie.Value, u)
if err != nil {
log.Printf("secure decode cookie: %v", err)
@@ -47,19 +47,13 @@ func (server *Server) withCookie(r *http.Request) (*authorize, error) {
return u, nil
}
func withUser(ctx context.Context) *authorize {
var result authorize
func withUser(ctx context.Context) Authorize {
var result Authorize
ctxValue, err := convert.ToByteE(ctx.Value(ContextUser))
log.Printf("ctx: %s", ctxValue)
if err != nil {
log.Printf("1: %v", err)
return nil
}
err = json.Unmarshal(ctxValue, &result)
if err != nil {
log.Printf("2: %v", err)
return nil
return result
}
return &result
json.Unmarshal(ctxValue, &result)
return result
}

View File

@@ -55,6 +55,7 @@ func (server *Server) setupRouter() {
router := mux.NewRouter()
router.Use(mux.CORSMethodMiddleware(router))
router.PathPrefix("/statics/").Handler(http.StripPrefix("/statics/", http.FileServer(http.Dir("web/statics"))))
router.PathPrefix("/upload/imgs").Handler(http.StripPrefix("/upload/imgs/", http.FileServer(http.Dir("upload/imgs"))))
router.HandleFunc("/register", server.registerView).Methods(http.MethodGet)
router.HandleFunc("/register", server.register).Methods(http.MethodPost)
@@ -68,8 +69,17 @@ func (server *Server) setupRouter() {
subRouter := router.PathPrefix("/").Subrouter()
subRouter.Use(server.authorizeMiddleware)
subRouter.HandleFunc("/upload", server.uploadView).Methods(http.MethodGet)
subRouter.HandleFunc("/upload", server.upload).Methods(http.MethodPost)
subRouter.HandleFunc("/me/videos", server.videosView).Methods(http.MethodGet)
subRouter.HandleFunc("/me/videos/p{page}", server.videosView).Methods(http.MethodGet)
subRouter.HandleFunc("/me/videos/create", server.createVideoView).Methods(http.MethodGet)
subRouter.HandleFunc("/me/videos/create/{xid}", server.createVideoView).Methods(http.MethodGet)
subRouter.HandleFunc("/me/videos/create", server.createVideo).Methods(http.MethodPost)
subRouter.HandleFunc("/upload_image", server.uploadImage).Methods(http.MethodPost)
subRouter.HandleFunc("/upload_file", server.uploadVideo).Methods(http.MethodPost)
subRouter.HandleFunc("/transfer/{xid}", server.transferView).Methods(http.MethodGet)
subRouter.HandleFunc("/transfer/{xid}", server.transfer).Methods(http.MethodPost)
server.router = router
}

View File

@@ -1,6 +1,8 @@
package handlers
import (
"bufio"
"errors"
"io"
"log"
"net/http"
@@ -13,17 +15,7 @@ import (
"github.com/zhang2092/mediahls/internal/pkg/fileutil"
)
func (server *Server) uploadView(w http.ResponseWriter, r *http.Request) {
user := withUser(r.Context())
log.Printf("%v", user)
renderUpload(w, nil)
}
func renderUpload(w http.ResponseWriter, data any) {
render(w, data, "web/templates/me/upload.html.tmpl", "web/templates/base/header.html.tmpl", "web/templates/base/footer.html.tmpl")
}
func (server *Server) upload(w http.ResponseWriter, r *http.Request) {
func (server *Server) uploadVideo(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
file, fileHeader, err := r.FormFile("file")
@@ -56,7 +48,8 @@ func (server *Server) upload(w http.ResponseWriter, r *http.Request) {
return
}
dir := path.Join("upload", time.Now().Format("20060102"))
curTime := time.Now()
dir := path.Join("upload", "files", curTime.Format("2006"), curTime.Format("01"), curTime.Format("02"))
exist, _ := fileutil.PathExists(dir)
if !exist {
err := os.MkdirAll(dir, os.ModePerm)
@@ -87,6 +80,60 @@ func (server *Server) upload(w http.ResponseWriter, r *http.Request) {
return
}
w.WriteHeader(http.StatusOK)
_, err = w.Write([]byte("/" + filePath))
if err != nil {
log.Printf("%v", err)
}
}
func (server *Server) uploadImage(w http.ResponseWriter, r *http.Request) {
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(r.Body)
_, fh, err := r.FormFile("file")
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, err = w.Write([]byte(err.Error()))
if err != nil {
log.Printf("%v", err)
}
return
}
f, err := fh.Open()
if err != nil {
log.Printf("%v", err)
w.WriteHeader(http.StatusInternalServerError)
_, err = w.Write([]byte("读取图片失败"))
if err != nil {
log.Printf("%v", err)
}
return
}
reader := bufio.NewReader(f)
filePath, err := fileutil.UploadImage(reader)
if errors.Is(err, fileutil.ErrUnsupportedFileFormat) {
log.Printf("%v", err)
w.WriteHeader(http.StatusUnsupportedMediaType)
_, err = w.Write([]byte(fileutil.ErrUnsupportedFileFormat.Error()))
if err != nil {
log.Printf("%v", err)
}
return
}
if err != nil {
log.Printf("%v", err)
w.WriteHeader(http.StatusInternalServerError)
_, err = w.Write([]byte(err.Error()))
if err != nil {
log.Printf("%v", err)
}
return
}
w.WriteHeader(http.StatusOK)
_, err = w.Write([]byte(filePath))
if err != nil {

View File

@@ -61,6 +61,7 @@ func viladatorRegister(email, username, password string) (*respErrs, bool) {
}
type respErrs struct {
Authorize
Summary string
Email string
Username string
@@ -191,7 +192,7 @@ func (server *Server) login(w http.ResponseWriter, r *http.Request) {
return
}
encoded, err := server.secureCookie.Encode(AuthorizeCookie, &authorize{AuthID: user.ID, AuthName: user.Username})
encoded, err := server.secureCookie.Encode(AuthorizeCookie, &Authorize{ID: user.ID, Name: user.Username})
if err != nil {
errs.Summary = "请求网络错误,请刷新重试(cookie)"
renderLogin(w, errs)

View File

@@ -1,28 +1,36 @@
package handlers
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/zhang2092/mediahls/internal/db"
"github.com/zhang2092/mediahls/internal/pkg/convert"
"github.com/zhang2092/mediahls/internal/pkg/fileutil"
"github.com/zhang2092/mediahls/internal/pkg/logger"
)
type playData struct {
AuthID string
AuthName string
Url string
Authorize
Url string
Video db.Video
}
func (server *Server) play(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
xid := vars["xid"]
video, _ := server.store.GetVideo(r.Context(), xid)
data := playData{
Url: "/media/" + xid + "/stream/",
Video: video,
}
auth, err := server.withCookie(r)
if err == nil {
data.AuthID = auth.AuthID
data.AuthName = auth.AuthName
data.Authorize = *auth
}
render(w, data, "web/templates/video/play.html.tmpl", "web/templates/base/header.html.tmpl", "web/templates/base/footer.html.tmpl")
}
@@ -43,10 +51,7 @@ http.ServeContent(w, r, "git", time.Now(), video)
func (server *Server) stream(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
mId := vars["xid"]
fmt.Println(mId)
segName, ok := vars["segName"]
if !ok {
mediaBase := getMediaBase(mId)
m3u8Name := "index.m3u8"
@@ -55,29 +60,261 @@ func (server *Server) stream(response http.ResponseWriter, request *http.Request
mediaBase := getMediaBase(mId)
serveHlsTs(response, request, mediaBase, segName)
}
}
func getMediaBase(mId string) string {
mediaRoot := "media"
return fmt.Sprintf("%s/%s", mediaRoot, mId)
}
func serveHlsM3u8(w http.ResponseWriter, r *http.Request, mediaBase, m3u8Name string) {
fmt.Println("serveHlsM3u8...")
mediaFile := fmt.Sprintf("%s/%s", mediaBase, m3u8Name)
fmt.Println(mediaFile)
http.ServeFile(w, r, mediaFile)
w.Header().Set("Content-Type", "application/x-mpegURL")
}
func serveHlsTs(w http.ResponseWriter, r *http.Request, mediaBase, segName string) {
fmt.Println("serveHlsTs...")
mediaFile := fmt.Sprintf("%s/%s", mediaBase, segName)
fmt.Println(mediaFile)
http.ServeFile(w, r, mediaFile)
w.Header().Set("Content-Type", "video/MP2T")
}
type meVideoData struct {
Authorize
Videos []db.Video
}
func (server *Server) videosView(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
data := meVideoData{
Authorize: withUser(ctx),
}
vars := mux.Vars(r)
page, err := strconv.Atoi(vars["page"])
if err != nil {
page = 1
}
videos, err := server.store.ListVideosByUser(ctx, db.ListVideosByUserParams{
UserID: data.Authorize.ID,
Limit: 16,
Offset: int32((page - 1) * 16),
})
if err == nil {
for _, item := range videos {
if len(item.Description) > 65 {
temp := strings.TrimSpace(item.Description[0:65]) + "..."
item.Description = temp
}
data.Videos = append(data.Videos, item)
}
}
render(w, data, "web/templates/me/videos.html.tmpl", "web/templates/base/header.html.tmpl", "web/templates/base/footer.html.tmpl")
}
func (server *Server) createVideoView(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
xid := vars["xid"]
vm := videoCreateResp{
Authorize: withUser(r.Context()),
}
if len(xid) > 0 {
if v, err := server.store.GetVideo(r.Context(), xid); err == nil {
vm.ID = v.ID
vm.Title = v.Title
vm.Images = v.Images
vm.Description = v.Description
vm.OriginLink = v.OriginLink
vm.Status = int(v.Status)
}
}
renderCreateVideo(w, vm)
}
func renderCreateVideo(w http.ResponseWriter, data any) {
render(w, data, "web/templates/video/edit.html.tmpl", "web/templates/base/header.html.tmpl", "web/templates/base/footer.html.tmpl")
}
type videoCreateResp struct {
Authorize
Summary string
ID string
IDErr string
Title string
TitleErr string
Images string
ImagesErr string
Description string
DescriptionErr string
OriginLink string
OriginLinkErr string
Status int
StatusErr string
}
func viladatorCreateVedio(r *http.Request) (*videoCreateResp, bool) {
ok := true
status, _ := strconv.Atoi(r.PostFormValue("status"))
errs := &videoCreateResp{
Authorize: withUser(r.Context()),
ID: r.PostFormValue("id"),
Title: r.PostFormValue("title"),
Images: r.PostFormValue("images"),
Description: r.PostFormValue("description"),
OriginLink: r.PostFormValue("origin_link"),
Status: status,
}
if len(errs.Title) == 0 {
errs.TitleErr = "请填写正确的标题"
ok = false
}
exist, _ := fileutil.PathExists(strings.TrimPrefix(errs.Images, "/"))
if !exist {
errs.ImagesErr = "请先上传图片"
ok = false
}
if len(errs.Description) == 0 {
errs.DescriptionErr = "请填写描述"
ok = false
}
exist, _ = fileutil.PathExists(strings.TrimPrefix(errs.OriginLink, "/"))
if !exist {
errs.OriginLinkErr = "请先上传视频"
ok = false
}
return errs, ok
}
func (server *Server) createVideo(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if err := r.ParseForm(); err != nil {
renderCreateVideo(w, videoCreateResp{Summary: "请求网络错误, 请刷新重试"})
return
}
vm, ok := viladatorCreateVedio(r)
if !ok {
renderCreateVideo(w, vm)
return
}
curTime := time.Now()
ctx := r.Context()
u := withUser(ctx)
if len(vm.ID) == 0 {
_, err := server.store.CreateVideo(ctx, db.CreateVideoParams{
ID: genId(),
Title: vm.Title,
Description: vm.Description,
Images: vm.Images,
OriginLink: vm.OriginLink,
PlayLink: "",
UserID: u.ID,
CreateBy: u.Name,
})
if err != nil {
vm.Summary = "添加视频失败"
renderCreateVideo(w, vm)
return
}
} else {
_, err := server.store.UpdateVideo(ctx, db.UpdateVideoParams{
ID: vm.ID,
Title: vm.Title,
Description: vm.Description,
Images: vm.Images,
Status: int32(vm.Status),
UpdateAt: curTime,
UpdateBy: u.Name,
})
if err != nil {
vm.Summary = "更新视频失败"
renderCreateVideo(w, vm)
return
}
}
http.Redirect(w, r, "/me/videos", http.StatusFound)
}
type transferData struct {
Authorize
Video db.Video
}
func (server *Server) transferView(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
xid := vars["xid"]
v, _ := server.store.GetVideo(r.Context(), xid)
data := transferData{
Video: v,
}
u, err := server.withCookie(r)
if err == nil {
data.Authorize = *u
}
render(w, data, "web/templates/video/transfer.html.tmpl", "web/templates/base/header.html.tmpl", "web/templates/base/footer.html.tmpl")
}
func (server *Server) transfer(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
xid := vars["xid"]
ctx := r.Context()
v, err := server.store.GetVideo(ctx, xid)
if err != nil {
http.Error(w, "视频信息错误", http.StatusInternalServerError)
return
}
u := withUser(ctx)
v, err = server.store.UpdateVideoStatus(ctx, db.UpdateVideoStatusParams{
ID: v.ID,
Status: 1,
UpdateAt: time.Now(),
UpdateBy: u.Name,
})
if err != nil {
http.Error(w, "视频转码错误", http.StatusInternalServerError)
return
}
go func(v db.Video, name string) {
ctx := context.Background()
err := convert.ConvertHLS("media/"+v.ID+"/", strings.TrimPrefix(v.OriginLink, "/"))
if err != nil {
logger.Logger.Errorf("Convert HLS [%s]-[%s]: %v", v.ID, v.OriginLink, err)
_, _ = server.store.UpdateVideoStatus(ctx, db.UpdateVideoStatusParams{
ID: v.ID,
Status: 2,
UpdateAt: time.Now(),
UpdateBy: name,
})
return
}
// 转码成功
if _, err = server.store.SetVideoPlay(ctx, db.SetVideoPlayParams{
ID: v.ID,
Status: 200,
PlayLink: "/media/" + v.ID + "/stream/",
UpdateAt: time.Now(),
UpdateBy: name,
}); err != nil {
logger.Logger.Errorf("Set Video Play [%s]-[%s]: %v", v.ID, v.OriginLink, err)
return
}
logger.Logger.Infof("[%s]-[%s] 转码完成", v.ID, v.OriginLink)
}(v, u.Name)
w.WriteHeader(http.StatusOK)
w.Write([]byte("视频正在转码中, 请稍后刷新页面"))
}

View File

@@ -1,6 +1,28 @@
package fileutil
import "os"
import (
"errors"
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"io"
"os"
"path"
"time"
nanoId "github.com/matoous/go-nanoid"
)
var (
// 图片最大数量
MaxImageSize = 10 << 20
ErrUnsupportedFileFormat = errors.New("图片格式不支持")
ErrFileStorePath = errors.New("文件存储路径异常")
ErrFileGenerateName = errors.New("文件名称生成异常")
ErrFileSaveFailed = errors.New("文件保存失败")
)
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
@@ -12,3 +34,60 @@ func PathExists(path string) (bool, error) {
}
return false, err
}
func Mkdir(path string) error {
return os.MkdirAll(path, os.ModePerm)
}
func UploadImage(r io.Reader) (string, error) {
img, format, err := image.Decode(r)
if err != nil {
return "", fmt.Errorf("读取图片错误")
}
if errors.Is(err, image.ErrFormat) {
return "", ErrUnsupportedFileFormat
}
if format != "png" && format != "jpeg" && format != "gif" {
return "", ErrUnsupportedFileFormat
}
// 存放目录
curTime := time.Now()
dir := path.Join("upload", "imgs", curTime.Format("2006"), curTime.Format("01"), curTime.Format("02"))
exist, _ := PathExists(dir)
if !exist {
err := Mkdir(dir)
if err != nil {
return "", ErrFileStorePath
}
}
filename, err := nanoId.Nanoid()
if err != nil {
return "", ErrFileGenerateName
}
filePath := path.Join("", dir, filename+"."+format)
f, err := os.Create(filePath)
if err != nil {
return "", ErrFileStorePath
}
defer func(f *os.File) {
_ = f.Close()
}(f)
var e error
switch format {
case "png":
e = png.Encode(f, img)
case "jpeg":
e = jpeg.Encode(f, img, nil)
case "gif":
e = gif.Encode(f, img, nil)
}
if e != nil {
return "", ErrFileSaveFailed
}
return "/" + filePath, nil
}