v0.1
This commit is contained in:
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
58
internal/db/query/video.sql
Normal file
58
internal/db/query/video.sql
Normal 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;
|
||||
@@ -1 +1,2 @@
|
||||
DROP TABLE "videos";
|
||||
DROP TABLE "users";
|
||||
@@ -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
337
internal/db/video.sql.go
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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("视频正在转码中, 请稍后刷新页面"))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user