Compare commits

..

3 Commits

Author SHA1 Message Date
fdbf26d751 fix: auth
Some checks failed
build quyun / Build (push) Failing after 1m26s
2025-12-20 11:18:59 +08:00
c42f2c651f udpate 2025-12-20 11:05:35 +08:00
788236ecc2 udpate 2025-12-20 10:21:14 +08:00
32 changed files with 330 additions and 194 deletions

3
.vscode/launch.json vendored
View File

@@ -9,7 +9,8 @@
"type": "go", "type": "go",
"request": "launch", "request": "launch",
"mode": "auto", "mode": "auto",
"program": "${workspaceFolder}", "cwd": "backend_v1",
"program": "${workspaceFolder}/backend_v1/main.go",
"args": [ "args": [
"serve" "serve"
], ],

View File

@@ -1,40 +1,39 @@
# .air.toml - Air 热重载配置文件 # .air.toml - Air 热重载配置文件
root = "." root = "."
testdata_dir = "testdata" testdata_dir = "testdata"
tmp_dir = "tmp" tmp_dir = "tmp"
[build] [build]
args_bin = [] args_bin = ["serve"]
bin = "./tmp/main" bin = "./tmp/main"
cmd = "go build -o ./tmp/main ." cmd = "go build -o ./tmp/main ."
delay = 1000 delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "frontend"] exclude_dir = ["assets", "tmp", "vendor", "testdata", "frontend"]
exclude_file = [] exclude_file = []
exclude_regex = ["_test.go"] exclude_regex = ["_test.go"]
exclude_unchanged = false exclude_unchanged = false
follow_symlink = false follow_symlink = false
full_bin = "" full_bin = ""
include_dir = [] include_dir = []
include_ext = ["go", "tpl", "tmpl", "html", "yaml", "yml", "toml"] include_ext = ["go", "tpl", "tmpl", "html", "yaml", "yml", "toml"]
kill_delay = "0s" kill_delay = "0s"
log = "build-errors.log" log = "build-errors.log"
send_interrupt = false send_interrupt = false
stop_on_root = false stop_on_root = false
[color] [color]
app = "" app = ""
build = "yellow" build = "yellow"
main = "magenta" main = "magenta"
runner = "green" runner = "green"
watcher = "cyan" watcher = "cyan"
[log] [log]
time = false time = false
[misc] [misc]
clean_on_exit = false clean_on_exit = false
[screen] [screen]
clear_on_rebuild = false clear_on_rebuild = false
keep_scroll = true keep_scroll = true

View File

@@ -5,8 +5,11 @@ import (
"quyun/v2/app/commands" "quyun/v2/app/commands"
"quyun/v2/app/errorx" "quyun/v2/app/errorx"
web "quyun/v2/app/http"
"quyun/v2/app/jobs" "quyun/v2/app/jobs"
"quyun/v2/app/middlewares"
_ "quyun/v2/docs" _ "quyun/v2/docs"
"quyun/v2/providers/ali"
"quyun/v2/providers/app" "quyun/v2/providers/app"
"quyun/v2/providers/http" "quyun/v2/providers/http"
"quyun/v2/providers/http/swagger" "quyun/v2/providers/http/swagger"
@@ -28,6 +31,7 @@ func defaultProviders() container.Providers {
http.DefaultProvider(), http.DefaultProvider(),
jwt.DefaultProvider(), jwt.DefaultProvider(),
job.DefaultProvider(), job.DefaultProvider(),
ali.DefaultProvider(),
}...) }...)
} }
@@ -38,8 +42,12 @@ func Command() atom.Option {
atom.RunE(Serve), atom.RunE(Serve),
atom.Providers( atom.Providers(
defaultProviders(). defaultProviders().
WithProviders(
web.Providers(),
).
With( With(
jobs.Provide, jobs.Provide,
middlewares.Provide,
), ),
), ),
) )
@@ -48,11 +56,12 @@ func Command() atom.Option {
type Service struct { type Service struct {
dig.In dig.In
App *app.Config App *app.Config
Job *job.Job Job *job.Job
Http *http.Service Middleware *middlewares.Middlewares
Initials []contracts.Initial `group:"initials"` Http *http.Service
Routes []contracts.HttpRoute `group:"routes"` Initials []contracts.Initial `group:"initials"`
Routes []contracts.HttpRoute `group:"routes"`
} }
func Serve(cmd *cobra.Command, args []string) error { func Serve(cmd *cobra.Command, args []string) error {
@@ -68,9 +77,10 @@ func Serve(cmd *cobra.Command, args []string) error {
svc.Http.Engine.Use(favicon.New(favicon.Config{ svc.Http.Engine.Use(favicon.New(favicon.Config{
Data: []byte{}, Data: []byte{},
})) }))
svc.Http.Engine.Use(svc.Middleware.DebugMode)
group := svc.Http.Engine.Group("")
for _, route := range svc.Routes { for _, route := range svc.Routes {
group := svc.Http.Engine.Group(route.Path(), route.Middlewares()...).Name(route.Name())
route.Register(group) route.Register(group)
} }

View File

@@ -27,7 +27,7 @@ type medias struct {
// @Bind pagination query // @Bind pagination query
// @Bind query query // @Bind query query
func (ctl *medias) List(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery) (*requests.Pager, error) { func (ctl *medias) List(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery) (*requests.Pager, error) {
return services.Medias.List(ctx, pagination, models.MediaQuery.Name.Like(database.WrapLike(*query.Keyword))) return services.Media.List(ctx, pagination, models.MediaQuery.Name.Like(database.WrapLike(*query.Keyword)))
} }
// Show media // Show media

View File

@@ -39,7 +39,7 @@ func (ctl *posts) List(ctx fiber.Ctx, pagination *requests.Pagination, query *Li
return nil, err return nil, err
} }
postIds := lo.Map(pager.Items.([]models.Post), func(item models.Post, _ int) int64 { postIds := lo.Map(pager.Items.([]*models.Post), func(item *models.Post, _ int) int64 {
return item.ID return item.ID
}) })
if len(postIds) > 0 { if len(postIds) > 0 {
@@ -48,13 +48,13 @@ func (ctl *posts) List(ctx fiber.Ctx, pagination *requests.Pagination, query *Li
return pager, err return pager, err
} }
items := lo.Map(pager.Items.([]models.Post), func(item models.Post, _ int) PostItem { items := lo.Map(pager.Items.([]*models.Post), func(item *models.Post, _ int) PostItem {
cnt := int64(0) cnt := int64(0)
if v, ok := postCntMap[item.ID]; ok { if v, ok := postCntMap[item.ID]; ok {
cnt = v cnt = v
} }
return PostItem{Post: &item, BoughtCount: cnt} return PostItem{Post: item, BoughtCount: cnt}
}) })
pager.Items = items pager.Items = items
@@ -97,7 +97,7 @@ func (ctl *posts) Create(ctx fiber.Ctx, form *PostForm) error {
} }
if form.Medias != nil { if form.Medias != nil {
medias, err := services.Medias.GetByIds(ctx, form.Medias) medias, err := services.Media.GetByIds(ctx, form.Medias)
if err != nil { if err != nil {
return err return err
} }
@@ -140,7 +140,7 @@ func (ctl *posts) Update(ctx fiber.Ctx, post *models.Post, form *PostForm) error
post.Tags = types.NewJSONType([]string{}) post.Tags = types.NewJSONType([]string{})
if form.Medias != nil { if form.Medias != nil {
medias, err := services.Medias.GetByIds(ctx, form.Medias) medias, err := services.Media.GetByIds(ctx, form.Medias)
if err != nil { if err != nil {
return err return err
} }
@@ -192,7 +192,7 @@ type PostItem struct {
// @Router /admin/posts/:id [get] // @Router /admin/posts/:id [get]
// @Bind post path key(id) model(id) // @Bind post path key(id) model(id)
func (ctl *posts) Show(ctx fiber.Ctx, post *models.Post) (*PostItem, error) { func (ctl *posts) Show(ctx fiber.Ctx, post *models.Post) (*PostItem, error) {
medias, err := services.Medias.GetByIds(ctx, lo.Map(post.Assets.Data(), func(asset fields.MediaAsset, _ int) int64 { medias, err := services.Media.GetByIds(ctx, lo.Map(post.Assets.Data(), func(asset fields.MediaAsset, _ int) int64 {
return asset.Media return asset.Media
})) }))
if err != nil { if err != nil {

View File

@@ -5,5 +5,7 @@ func (r *Routes) Path() string {
} }
func (r *Routes) Middlewares() []any { func (r *Routes) Middlewares() []any {
return []any{} return []any{
r.middlewares.AuthAdmin,
}
} }

View File

@@ -47,7 +47,7 @@ func (s *statistics) statistics(ctx fiber.Ctx) (*StatisticsResponse, error) {
return nil, err return nil, err
} }
statistics.Media, err = services.Medias.Count(ctx) statistics.Media, err = services.Media.Count(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -46,7 +46,7 @@ type PreCheckResp struct {
// @Bind ext path // @Bind ext path
// @Bind mime query // @Bind mime query
func (up *uploads) PreUploadCheck(ctx fiber.Ctx, md5, ext, mime string) (*PreCheckResp, error) { func (up *uploads) PreUploadCheck(ctx fiber.Ctx, md5, ext, mime string) (*PreCheckResp, error) {
_, err := services.Medias.GetByHash(ctx, md5) _, err := services.Media.GetByHash(ctx, md5)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
preSign, err := up.oss.PreSignUpload(ctx, fmt.Sprintf("%s.%s", md5, ext), mime) preSign, err := up.oss.PreSignUpload(ctx, fmt.Sprintf("%s.%s", md5, ext), mime)
if err != nil { if err != nil {
@@ -77,7 +77,7 @@ type PostUploadedForm struct {
// @Router /admin/uploads/post-uploaded-action [post] // @Router /admin/uploads/post-uploaded-action [post]
// @Bind body body // @Bind body body
func (up *uploads) PostUploadedAction(ctx fiber.Ctx, body *PostUploadedForm) error { func (up *uploads) PostUploadedAction(ctx fiber.Ctx, body *PostUploadedForm) error {
m, err := services.Medias.GetByHash(ctx, body.Md5) m, err := services.Media.GetByHash(ctx, body.Md5)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err return err
} }

View File

@@ -42,7 +42,7 @@ type posts struct {
// @Param pagination query requests.Pagination false "分页参数" // @Param pagination query requests.Pagination false "分页参数"
// @Param query query ListQuery false "筛选条件" // @Param query query ListQuery false "筛选条件"
// @Success 200 {object} requests.Pager{items=PostItem} "成功" // @Success 200 {object} requests.Pager{items=PostItem} "成功"
// @Router /posts [get] // @Router /v1/posts [get]
// @Bind pagination query // @Bind pagination query
// @Bind query query // @Bind query query
// @Bind user local // @Bind user local
@@ -55,7 +55,11 @@ func (ctl *posts) List(
tbl, _ := models.PostQuery.QueryContext(ctx) tbl, _ := models.PostQuery.QueryContext(ctx)
conds := []gen.Condition{ conds := []gen.Condition{
tbl.Status.Eq(fields.PostStatusPublished), tbl.Status.Eq(fields.PostStatusPublished),
tbl.Title.Like(database.WrapLike(*query.Keyword)), }
if query.Keyword != nil && *query.Keyword != "" {
conds = append(conds,
tbl.Title.Like(database.WrapLike(*query.Keyword)),
)
} }
pager, err := services.Posts.List(ctx, pagination, conds...) pager, err := services.Posts.List(ctx, pagination, conds...)
@@ -64,14 +68,14 @@ func (ctl *posts) List(
return nil, err return nil, err
} }
postIds := lo.Map(pager.Items.([]models.Post), func(item models.Post, _ int) int64 { return item.ID }) postIds := lo.Map(pager.Items.([]*models.Post), func(item *models.Post, _ int) int64 { return item.ID })
if len(postIds) > 0 { if len(postIds) > 0 {
userBoughtIds, err := services.Users.BatchCheckHasBought(ctx, user.ID, postIds) userBoughtIds, err := services.Users.BatchCheckHasBought(ctx, user.ID, postIds)
if err != nil { if err != nil {
log.WithError(err).Errorf("BatchCheckHasBought err: %v", err) log.WithError(err).Errorf("BatchCheckHasBought err: %v", err)
} }
items := lo.FilterMap(pager.Items.([]models.Post), func(item models.Post, _ int) (PostItem, bool) { items := lo.FilterMap(pager.Items.([]*models.Post), func(item *models.Post, _ int) (PostItem, bool) {
medias, err := services.Posts.GetMediasByIds(ctx, item.HeadImages.Data()) medias, err := services.Posts.GetMediasByIds(ctx, item.HeadImages.Data())
if err != nil { if err != nil {
log.Errorf("GetMediaByIds err: %v", err) log.Errorf("GetMediaByIds err: %v", err)
@@ -132,7 +136,7 @@ type PostItem struct {
// @Produce json // @Produce json
// @Param id path int64 true "作品 ID" // @Param id path int64 true "作品 ID"
// @Success 200 {object} PostItem "成功" // @Success 200 {object} PostItem "成功"
// @Router /posts/:id/show [get] // @Router /v1/posts/:id/show [get]
// @Bind post path key(id) model(id) // @Bind post path key(id) model(id)
// @Bind user local // @Bind user local
func (ctl *posts) Show(ctx fiber.Ctx, post *models.Post, user *models.User) (*PostItem, error) { func (ctl *posts) Show(ctx fiber.Ctx, post *models.Post, user *models.User) (*PostItem, error) {
@@ -187,7 +191,7 @@ type PlayUrl struct {
// @Produce json // @Produce json
// @Param id path int64 true "作品 ID" // @Param id path int64 true "作品 ID"
// @Success 200 {object} PlayUrl "成功" // @Success 200 {object} PlayUrl "成功"
// @Router /posts/:id/play [get] // @Router /v1/posts/:id/play [get]
// @Bind post path key(id) model(id) // @Bind post path key(id) model(id)
// @Bind user local // @Bind user local
func (ctl *posts) Play(ctx fiber.Ctx, post *models.Post, user *models.User) (*PlayUrl, error) { func (ctl *posts) Play(ctx fiber.Ctx, post *models.Post, user *models.User) (*PlayUrl, error) {
@@ -207,7 +211,7 @@ func (ctl *posts) Play(ctx fiber.Ctx, post *models.Post, user *models.User) (*Pl
for _, asset := range post.Assets.Data() { for _, asset := range post.Assets.Data() {
if asset.Type == "video/mp4" && asset.Metas != nil && asset.Metas.Short == preview { if asset.Type == "video/mp4" && asset.Metas != nil && asset.Metas.Short == preview {
media, err := services.Medias.FindByID(ctx, asset.Media) media, err := services.Media.FindByID(ctx, asset.Media)
if err != nil { if err != nil {
log.WithError(err).Errorf("medias GetByID err: %v", err) log.WithError(err).Errorf("medias GetByID err: %v", err)
return nil, err return nil, err
@@ -239,7 +243,7 @@ func (ctl *posts) Play(ctx fiber.Ctx, post *models.Post, user *models.User) (*Pl
// @Param pagination query requests.Pagination false "分页参数" // @Param pagination query requests.Pagination false "分页参数"
// @Param query query ListQuery false "筛选条件" // @Param query query ListQuery false "筛选条件"
// @Success 200 {object} requests.Pager{items=PostItem} "成功" // @Success 200 {object} requests.Pager{items=PostItem} "成功"
// @Router /posts/mine [get] // @Router /v1/posts/mine [get]
// @Bind pagination query // @Bind pagination query
// @Bind query query // @Bind query query
// @Bind user local // @Bind user local
@@ -253,7 +257,11 @@ func (ctl *posts) Mine(
conds := []gen.Condition{ conds := []gen.Condition{
models.PostQuery.Status.Eq(fields.PostStatusPublished), models.PostQuery.Status.Eq(fields.PostStatusPublished),
models.PostQuery.Title.Like(database.WrapLike(*query.Keyword)), }
if query.Keyword != nil && *query.Keyword != "" {
conds = append(conds,
models.PostQuery.Title.Like(database.WrapLike(*query.Keyword)),
)
} }
pager, err := services.Users.PostList(ctx, user.ID, pagination, conds...) pager, err := services.Users.PostList(ctx, user.ID, pagination, conds...)
@@ -265,7 +273,7 @@ func (ctl *posts) Mine(
postIds := lo.Map(pager.Items.([]*models.Post), func(item *models.Post, _ int) int64 { return item.ID }) postIds := lo.Map(pager.Items.([]*models.Post), func(item *models.Post, _ int) int64 { return item.ID })
if len(postIds) > 0 { if len(postIds) > 0 {
items := lo.FilterMap(pager.Items.([]*models.Post), func(item *models.Post, _ int) (PostItem, bool) { items := lo.FilterMap(pager.Items.([]*models.Post), func(item *models.Post, _ int) (PostItem, bool) {
medias, err := services.Medias.GetByIds(ctx, item.HeadImages.Data()) medias, err := services.Media.GetByIds(ctx, item.HeadImages.Data())
if err != nil { if err != nil {
log.Errorf("GetMediaByIds err: %v", err) log.Errorf("GetMediaByIds err: %v", err)
return PostItem{}, false return PostItem{}, false
@@ -306,7 +314,7 @@ func (ctl *posts) Mine(
// @Produce json // @Produce json
// @Param id path int64 true "作品 ID" // @Param id path int64 true "作品 ID"
// @Success 200 {object} wechat.JSAPIPayParams "成功(余额支付返回 AppId=balance" // @Success 200 {object} wechat.JSAPIPayParams "成功(余额支付返回 AppId=balance"
// @Router /posts/:id/buy [post] // @Router /v1/posts/:id/buy [post]
// @Bind post path key(id) model(id) // @Bind post path key(id) model(id)
// @Bind user local // @Bind user local
func (ctl *posts) Buy(ctx fiber.Ctx, post *models.Post, user *models.User) (*wechat.JSAPIPayParams, error) { func (ctl *posts) Buy(ctx fiber.Ctx, post *models.Post, user *models.User) (*wechat.JSAPIPayParams, error) {

View File

@@ -45,15 +45,15 @@ func (r *Routes) Name() string {
// Each route is registered with its corresponding controller action and parameter bindings. // Each route is registered with its corresponding controller action and parameter bindings.
func (r *Routes) Register(router fiber.Router) { func (r *Routes) Register(router fiber.Router) {
// Register routes for controller: posts // Register routes for controller: posts
r.log.Debugf("Registering route: Get /posts -> posts.List") r.log.Debugf("Registering route: Get /v1/posts -> posts.List")
router.Get("/posts"[len(r.Path()):], DataFunc3( router.Get("/v1/posts"[len(r.Path()):], DataFunc3(
r.posts.List, r.posts.List,
Query[requests.Pagination]("pagination"), Query[requests.Pagination]("pagination"),
Query[ListQuery]("query"), Query[ListQuery]("query"),
Local[*models.User]("user"), Local[*models.User]("user"),
)) ))
r.log.Debugf("Registering route: Get /posts/:id/play -> posts.Play") r.log.Debugf("Registering route: Get /v1/posts/:id/play -> posts.Play")
router.Get("/posts/:id/play"[len(r.Path()):], DataFunc2( router.Get("/v1/posts/:id/play"[len(r.Path()):], DataFunc2(
r.posts.Play, r.posts.Play,
func(ctx fiber.Ctx) (*models.Post, error) { func(ctx fiber.Ctx) (*models.Post, error) {
v := fiber.Params[int](ctx, "id") v := fiber.Params[int](ctx, "id")
@@ -61,8 +61,8 @@ func (r *Routes) Register(router fiber.Router) {
}, },
Local[*models.User]("user"), Local[*models.User]("user"),
)) ))
r.log.Debugf("Registering route: Get /posts/:id/show -> posts.Show") r.log.Debugf("Registering route: Get /v1/posts/:id/show -> posts.Show")
router.Get("/posts/:id/show"[len(r.Path()):], DataFunc2( router.Get("/v1/posts/:id/show"[len(r.Path()):], DataFunc2(
r.posts.Show, r.posts.Show,
func(ctx fiber.Ctx) (*models.Post, error) { func(ctx fiber.Ctx) (*models.Post, error) {
v := fiber.Params[int](ctx, "id") v := fiber.Params[int](ctx, "id")
@@ -70,15 +70,15 @@ func (r *Routes) Register(router fiber.Router) {
}, },
Local[*models.User]("user"), Local[*models.User]("user"),
)) ))
r.log.Debugf("Registering route: Get /posts/mine -> posts.Mine") r.log.Debugf("Registering route: Get /v1/posts/mine -> posts.Mine")
router.Get("/posts/mine"[len(r.Path()):], DataFunc3( router.Get("/v1/posts/mine"[len(r.Path()):], DataFunc3(
r.posts.Mine, r.posts.Mine,
Query[requests.Pagination]("pagination"), Query[requests.Pagination]("pagination"),
Query[ListQuery]("query"), Query[ListQuery]("query"),
Local[*models.User]("user"), Local[*models.User]("user"),
)) ))
r.log.Debugf("Registering route: Post /posts/:id/buy -> posts.Buy") r.log.Debugf("Registering route: Post /v1/posts/:id/buy -> posts.Buy")
router.Post("/posts/:id/buy"[len(r.Path()):], DataFunc2( router.Post("/v1/posts/:id/buy"[len(r.Path()):], DataFunc2(
r.posts.Buy, r.posts.Buy,
func(ctx fiber.Ctx) (*models.Post, error) { func(ctx fiber.Ctx) (*models.Post, error) {
v := fiber.Params[int](ctx, "id") v := fiber.Params[int](ctx, "id")
@@ -87,13 +87,13 @@ func (r *Routes) Register(router fiber.Router) {
Local[*models.User]("user"), Local[*models.User]("user"),
)) ))
// Register routes for controller: users // Register routes for controller: users
r.log.Debugf("Registering route: Get /users/profile -> users.Profile") r.log.Debugf("Registering route: Get /v1/users/profile -> users.Profile")
router.Get("/users/profile"[len(r.Path()):], DataFunc1( router.Get("/v1/users/profile"[len(r.Path()):], DataFunc1(
r.users.Profile, r.users.Profile,
Local[*models.User]("user"), Local[*models.User]("user"),
)) ))
r.log.Debugf("Registering route: Put /users/username -> users.Update") r.log.Debugf("Registering route: Put /v1/users/username -> users.Update")
router.Put("/users/username"[len(r.Path()):], Func2( router.Put("/v1/users/username"[len(r.Path()):], Func2(
r.users.Update, r.users.Update,
Local[*models.User]("user"), Local[*models.User]("user"),
Body[ProfileForm]("form"), Body[ProfileForm]("form"),

View File

@@ -1,9 +1,11 @@
package http package http
func (r *Routes) Path() string { func (r *Routes) Path() string {
return "/http" return "/v1"
} }
func (r *Routes) Middlewares() []any { func (r *Routes) Middlewares() []any {
return []any{} return []any{
r.middlewares.AuthFrontend,
}
} }

View File

@@ -27,7 +27,7 @@ type UserInfo struct {
// @Tags Users // @Tags Users
// @Produce json // @Produce json
// @Success 200 {object} UserInfo "成功" // @Success 200 {object} UserInfo "成功"
// @Router /users/profile [get] // @Router /v1/users/profile [get]
// @Bind user local // @Bind user local
func (ctl *users) Profile(ctx fiber.Ctx, user *models.User) (*UserInfo, error) { func (ctl *users) Profile(ctx fiber.Ctx, user *models.User) (*UserInfo, error) {
return &UserInfo{ return &UserInfo{
@@ -51,7 +51,7 @@ type ProfileForm struct {
// @Produce json // @Produce json
// @Param form body ProfileForm true "请求体" // @Param form body ProfileForm true "请求体"
// @Success 200 {object} any "成功" // @Success 200 {object} any "成功"
// @Router /users/username [put] // @Router /v1/users/username [put]
// @Bind user local // @Bind user local
// @Bind form body // @Bind form body
func (ctl *users) Update(ctx fiber.Ctx, user *models.User, form *ProfileForm) error { func (ctl *users) Update(ctx fiber.Ctx, user *models.User, form *ProfileForm) error {

View File

@@ -53,7 +53,7 @@ func (w *DownloadFromAliOSSWorker) Work(ctx context.Context, job *Job[DownloadFr
log.Infof("[Start] Working on job with strings: %+v", job.Args) log.Infof("[Start] Working on job with strings: %+v", job.Args)
defer log.Infof("[End] Finished %s", job.Args.Kind()) defer log.Infof("[End] Finished %s", job.Args.Kind())
media, err := services.Medias.GetByHash(ctx, job.Args.MediaHash) media, err := services.Media.GetByHash(ctx, job.Args.MediaHash)
if err != nil { if err != nil {
log.Errorf("Error getting media by ID: %v", err) log.Errorf("Error getting media by ID: %v", err)
return JobCancel(err) return JobCancel(err)

View File

@@ -58,13 +58,13 @@ func (w *PublishDraftPostsWorker) Work(ctx context.Context, job *Job[PublishDraf
log.Infof("[Start] Working on job with strings: %+v", job.Args) log.Infof("[Start] Working on job with strings: %+v", job.Args)
defer log.Infof("[End] Finished %s", job.Args.Kind()) defer log.Infof("[End] Finished %s", job.Args.Kind())
media, err := services.Medias.GetByHash(ctx, job.Args.MediaHash) media, err := services.Media.GetByHash(ctx, job.Args.MediaHash)
if err != nil { if err != nil {
log.Errorf("Error getting media by ID: %v", err) log.Errorf("Error getting media by ID: %v", err)
return JobCancel(err) return JobCancel(err)
} }
relationMedias, err := services.Medias.GetRelations(ctx, media.Hash) relationMedias, err := services.Media.GetRelations(ctx, media.Hash)
if err != nil { if err != nil {
log.Errorf("Error getting relation medias: %v", err) log.Errorf("Error getting relation medias: %v", err)
return JobCancel(err) return JobCancel(err)

View File

@@ -54,7 +54,7 @@ func (w *VideoCutWorker) Work(ctx context.Context, job *Job[VideoCut]) error {
log.Infof("[Start] Working on job with strings: %+v", job.Args) log.Infof("[Start] Working on job with strings: %+v", job.Args)
defer log.Infof("[End] Finished %s", job.Args.Kind()) defer log.Infof("[End] Finished %s", job.Args.Kind())
media, err := services.Medias.GetByHash(ctx, job.Args.MediaHash) media, err := services.Media.GetByHash(ctx, job.Args.MediaHash)
if err != nil { if err != nil {
log.Errorf("Error getting media by ID: %v", err) log.Errorf("Error getting media by ID: %v", err)
return JobCancel(err) return JobCancel(err)
@@ -81,7 +81,7 @@ func (w *VideoCutWorker) Work(ctx context.Context, job *Job[VideoCut]) error {
Short: false, Short: false,
Duration: duration, Duration: duration,
} }
if err := services.Medias.UpdateMetas(ctx, media.ID, metas); err != nil { if err := services.Media.UpdateMetas(ctx, media.ID, metas); err != nil {
log.Errorf("Error updating media metas: %v", err) log.Errorf("Error updating media metas: %v", err)
return errors.Wrap(err, "update media metas") return errors.Wrap(err, "update media metas")
} }

View File

@@ -59,7 +59,7 @@ func (w *VideoExtractHeadImageWorker) Work(ctx context.Context, job *Job[VideoEx
log.Infof("[Start] Working on job with strings: %+v", job.Args) log.Infof("[Start] Working on job with strings: %+v", job.Args)
defer log.Infof("[End] Finished %s", job.Args.Kind()) defer log.Infof("[End] Finished %s", job.Args.Kind())
media, err := services.Medias.GetByHash(ctx, job.Args.MediaHash) media, err := services.Media.GetByHash(ctx, job.Args.MediaHash)
if err != nil { if err != nil {
log.Errorf("Error getting media by ID: %v", err) log.Errorf("Error getting media by ID: %v", err)
return JobCancel(err) return JobCancel(err)

View File

@@ -59,7 +59,7 @@ func (w *VideoStoreShortWorker) Work(ctx context.Context, job *Job[VideoStoreSho
log.Infof("[Start] Working on job with strings: %+v", job.Args) log.Infof("[Start] Working on job with strings: %+v", job.Args)
defer log.Infof("[End] Finished %s", job.Args.Kind()) defer log.Infof("[End] Finished %s", job.Args.Kind())
media, err := services.Medias.GetByHash(ctx, job.Args.MediaHash) media, err := services.Media.GetByHash(ctx, job.Args.MediaHash)
if err != nil { if err != nil {
log.Errorf("Error getting media by ID: %v", err) log.Errorf("Error getting media by ID: %v", err)
return JobCancel(err) return JobCancel(err)

View File

@@ -0,0 +1,73 @@
package middlewares
import (
"net/url"
"strings"
"quyun/v2/app/services"
"quyun/v2/pkg/utils"
"github.com/gofiber/fiber/v3"
log "github.com/sirupsen/logrus"
)
func (f *Middlewares) AuthFrontend(ctx fiber.Ctx) error {
if strings.HasPrefix(ctx.Path(), "/v1/auth/") {
return ctx.Next()
}
if f.app.IsDevMode() && true {
user, err := services.Users.FindByID(ctx.Context(), 1001)
if err != nil {
return ctx.Send([]byte("User not found"))
}
ctx.Locals("user", user)
return ctx.Next()
}
fullUrl := utils.FullURI(ctx)
u, err := url.Parse(fullUrl)
if err != nil {
return err
}
query := u.Query()
query.Set("redirect", fullUrl)
u.RawQuery = query.Encode()
u.Path = "/v1/auth/wechat" // TODO: use phone validation
fullUrl = u.String()
// check cookie exists
cookie := ctx.Cookies("token")
log.Infof("cookie: %s", cookie)
if cookie == "" {
log.Infof("auth redirect_uri: %s", fullUrl)
if ctx.XHR() {
return ctx.SendStatus(fiber.StatusUnauthorized)
}
return ctx.Redirect().To(fullUrl)
}
jwt, err := f.jwt.Parse(cookie)
if err != nil {
// remove cookie
ctx.ClearCookie("token")
if ctx.XHR() {
return ctx.SendStatus(fiber.StatusUnauthorized)
}
return ctx.Redirect().To(fullUrl)
}
user, err := services.Users.FindByID(ctx.Context(), jwt.UserID)
if err != nil {
// remove cookie
ctx.ClearCookie("token")
if ctx.XHR() {
return ctx.SendStatus(fiber.StatusUnauthorized)
}
return ctx.Redirect().To(fullUrl)
}
ctx.Locals("user", user)
return ctx.Next()
}

View File

@@ -0,0 +1,34 @@
package middlewares
import (
"strings"
"github.com/gofiber/fiber/v3"
)
func (f *Middlewares) AuthAdmin(ctx fiber.Ctx) error {
if !strings.HasPrefix(ctx.Path(), "/v1/admin") {
return ctx.Next()
}
if ctx.Path() == "/v1/admin/auth" {
return ctx.Next()
}
token := ctx.Get("Authorization")
if token == "" {
token = ctx.Query("token")
if token == "" {
return ctx.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
}
}
jwt, err := f.jwt.Parse(token)
if err != nil {
return ctx.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
}
if jwt.UserID != -20140202 {
return ctx.Status(fiber.StatusForbidden).SendString("Forbidden")
}
return ctx.Next()
}

View File

@@ -0,0 +1,19 @@
package middlewares
import (
"strings"
"github.com/gofiber/fiber/v3"
)
func (f *Middlewares) WechatMpVerify(ctx fiber.Ctx) error {
if !strings.HasPrefix(ctx.Path(), "/MP_verify_") {
return ctx.Next()
}
path := strings.Replace(ctx.Path(), "MP_verify_", "", 1)
path = strings.Replace(path, ".txt", "", 1)
path = strings.Trim(path, "/")
return ctx.SendString(path)
}

View File

@@ -1,12 +1,17 @@
package middlewares package middlewares
import ( import (
"quyun/v2/providers/app"
"quyun/v2/providers/jwt"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// @provider // @provider
type Middlewares struct { type Middlewares struct {
log *log.Entry `inject:"false"` log *log.Entry `inject:"false"`
app *app.Config
jwt *jwt.JWT
} }
func (f *Middlewares) Prepare() error { func (f *Middlewares) Prepare() error {

View File

@@ -1,13 +1,22 @@
package middlewares package middlewares
import ( import (
"quyun/v2/providers/app"
"quyun/v2/providers/jwt"
"go.ipao.vip/atom/container" "go.ipao.vip/atom/container"
"go.ipao.vip/atom/opt" "go.ipao.vip/atom/opt"
) )
func Provide(opts ...opt.Option) error { func Provide(opts ...opt.Option) error {
if err := container.Container.Provide(func() (*Middlewares, error) { if err := container.Container.Provide(func(
obj := &Middlewares{} app *app.Config,
jwt *jwt.JWT,
) (*Middlewares, error) {
obj := &Middlewares{
app: app,
jwt: jwt,
}
if err := obj.Prepare(); err != nil { if err := obj.Prepare(); err != nil {
return nil, err return nil, err
} }

View File

@@ -12,9 +12,9 @@ import (
) )
// @provider // @provider
type medias struct{} type media struct{}
func (m *medias) List( func (m *media) List(
ctx context.Context, ctx context.Context,
pagination *requests.Pagination, pagination *requests.Pagination,
conds ...gen.Condition, conds ...gen.Condition,
@@ -39,7 +39,7 @@ func (m *medias) List(
} }
// GetByIds // GetByIds
func (m *medias) GetByIds(ctx context.Context, ids []int64) ([]*models.Media, error) { func (m *media) GetByIds(ctx context.Context, ids []int64) ([]*models.Media, error) {
if len(ids) == 0 { if len(ids) == 0 {
return []*models.Media{}, nil return []*models.Media{}, nil
} }
@@ -57,7 +57,7 @@ func (m *medias) GetByIds(ctx context.Context, ids []int64) ([]*models.Media, er
} }
// GetByHash // GetByHash
func (m *medias) GetByHash(ctx context.Context, hash string) (*models.Media, error) { func (m *media) GetByHash(ctx context.Context, hash string) (*models.Media, error) {
tbl, query := models.MediaQuery.QueryContext(ctx) tbl, query := models.MediaQuery.QueryContext(ctx)
item, err := query. item, err := query.
Where(tbl.Hash.Eq(hash)). Where(tbl.Hash.Eq(hash)).
@@ -69,7 +69,7 @@ func (m *medias) GetByHash(ctx context.Context, hash string) (*models.Media, err
} }
// UpdateMetas // UpdateMetas
func (m *medias) UpdateMetas(ctx context.Context, id int64, metas fields.MediaMetas) error { func (m *media) UpdateMetas(ctx context.Context, id int64, metas fields.MediaMetas) error {
tbl, query := models.MediaQuery.QueryContext(ctx) tbl, query := models.MediaQuery.QueryContext(ctx)
_, err := query. _, err := query.
Where(tbl.ID.Eq(id)). Where(tbl.ID.Eq(id)).
@@ -82,13 +82,13 @@ func (m *medias) UpdateMetas(ctx context.Context, id int64, metas fields.MediaMe
// GetRelationMedias // GetRelationMedias
func (m *medias) GetRelations(ctx context.Context, hash string) ([]*models.Media, error) { func (m *media) GetRelations(ctx context.Context, hash string) ([]*models.Media, error) {
tbl, query := models.MediaQuery.QueryContext(ctx) tbl, query := models.MediaQuery.QueryContext(ctx)
return query.Where(tbl.Metas.KeyEq("parent_hash", hash)).Find() return query.Where(tbl.Metas.KeyEq("parent_hash", hash)).Find()
} }
// FindByID // FindByID
func (m *medias) FindByID(ctx context.Context, id int64) (*models.Media, error) { func (m *media) FindByID(ctx context.Context, id int64) (*models.Media, error) {
tbl, query := models.MediaQuery.QueryContext(ctx) tbl, query := models.MediaQuery.QueryContext(ctx)
item, err := query.Where(tbl.ID.Eq(id)).First() item, err := query.Where(tbl.ID.Eq(id)).First()
if err != nil { if err != nil {
@@ -98,7 +98,7 @@ func (m *medias) FindByID(ctx context.Context, id int64) (*models.Media, error)
} }
// Count // Count
func (m *medias) Count(ctx context.Context, conds ...gen.Condition) (int64, error) { func (m *media) Count(ctx context.Context, conds ...gen.Condition) (int64, error) {
_, query := models.MediaQuery.QueryContext(ctx) _, query := models.MediaQuery.QueryContext(ctx)
if len(conds) > 0 { if len(conds) > 0 {
query = query.Where(conds...) query = query.Where(conds...)

View File

@@ -9,8 +9,8 @@ import (
) )
func Provide(opts ...opt.Option) error { func Provide(opts ...opt.Option) error {
if err := container.Container.Provide(func() (*medias, error) { if err := container.Container.Provide(func() (*media, error) {
obj := &medias{} obj := &media{}
return obj, nil return obj, nil
}); err != nil { }); err != nil {
@@ -32,14 +32,14 @@ func Provide(opts ...opt.Option) error {
} }
if err := container.Container.Provide(func( if err := container.Container.Provide(func(
db *gorm.DB, db *gorm.DB,
medias *medias, media *media,
orders *orders, orders *orders,
posts *posts, posts *posts,
users *users, users *users,
) (contracts.Initial, error) { ) (contracts.Initial, error) {
obj := &services{ obj := &services{
db: db, db: db,
medias: medias, media: media,
orders: orders, orders: orders,
posts: posts, posts: posts,
users: users, users: users,

View File

@@ -8,7 +8,7 @@ var _db *gorm.DB
// exported CamelCase Services // exported CamelCase Services
var ( var (
Medias *medias Media *media
Orders *orders Orders *orders
Posts *posts Posts *posts
Users *users Users *users
@@ -18,7 +18,7 @@ var (
type services struct { type services struct {
db *gorm.DB db *gorm.DB
// define Services // define Services
medias *medias media *media
orders *orders orders *orders
posts *posts posts *posts
users *users users *users
@@ -28,7 +28,7 @@ func (svc *services) Prepare() error {
_db = svc.db _db = svc.db
// set exported Services here // set exported Services here
Medias = svc.medias Media = svc.media
Orders = svc.orders Orders = svc.orders
Posts = svc.posts Posts = svc.posts
Users = svc.users Users = svc.users

View File

@@ -56,39 +56,26 @@ func (m *users) PostList(
// OFFSET(pagination.Offset) // OFFSET(pagination.Offset)
// m.log().Infof("sql: %s", stmt.DebugSql()) // m.log().Infof("sql: %s", stmt.DebugSql())
// var posts []Posts tbl, query := models.UserPostQuery.QueryContext(ctx)
// err := stmt.QueryContext(ctx, db, &posts) pagePosts, cnt, err := query.Select(tbl.PostID).
// if err != nil { Where(tbl.UserID.Eq(userId)).
// if errors.Is(err, qrm.ErrNoRows) { FindByPage(int(pagination.Offset()), int(pagination.Limit))
// return &requests.Pager{
// Items: nil,
// Total: 0,
// Pagination: *pagination,
// }, nil
// }
// m.log().Errorf("error querying posts: %v", err)
// return nil, err
// }
// // total count if err != nil {
// var cnt struct { return nil, err
// Cnt int64 }
// } postIds := lo.Map(pagePosts, func(item *models.UserPost, _ int) int64 { return item.PostID })
// stmtCnt := tblUserPosts.SELECT(COUNT(tblUserPosts.ID).AS("cnt")).WHERE(tblUserPosts.UserID.EQ(Int64(userId))) postTbl, postQuery := models.PostQuery.QueryContext(ctx)
// m.log().Infof("sql: %s", stmtCnt.DebugSql()) items, err := postQuery.Where(postTbl.ID.In(postIds...)).Find()
if err != nil {
// if err := stmtCnt.QueryContext(ctx, db, &cnt); err != nil { return nil, err
// m.log().Errorf("error counting users: %v", err) }
// return nil, err return &requests.Pager{
// } Items: items,
Total: cnt,
// return &requests.Pager{ Pagination: *pagination,
// Items: posts, }, nil
// Total: cnt.Cnt,
// Pagination: *pagination,
// }, nil
return nil, nil
} }
// GetUsersMapByIDs // GetUsersMapByIDs

View File

@@ -34,7 +34,7 @@ require (
github.com/spf13/cobra v1.10.1 github.com/spf13/cobra v1.10.1
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/swaggo/files/v2 v2.0.2 github.com/swaggo/files/v2 v2.0.2
go.ipao.vip/atom v1.2.1 go.ipao.vip/atom v1.3.1
go.ipao.vip/gen v0.0.0-20250924024520-70c4accdea44 go.ipao.vip/gen v0.0.0-20250924024520-70c4accdea44
go.uber.org/dig v1.19.0 go.uber.org/dig v1.19.0
golang.org/x/net v0.48.0 golang.org/x/net v0.48.0

View File

@@ -325,8 +325,12 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.ipao.vip/atom v1.2.0 h1:Be3ZmvYkENQMUl+ITOxvgYi+GrcygplIAYL4aNH9kpA=
go.ipao.vip/atom v1.2.0/go.mod h1:woAv+rZf0xd+7mEtKWv4PyazQARFLnrV/qA4qlAK008=
go.ipao.vip/atom v1.2.1 h1:7VlDLSkGNVEZLVM/JVcXXdMTO0+sFsxe1vfIM4Xz8uc= go.ipao.vip/atom v1.2.1 h1:7VlDLSkGNVEZLVM/JVcXXdMTO0+sFsxe1vfIM4Xz8uc=
go.ipao.vip/atom v1.2.1/go.mod h1:woAv+rZf0xd+7mEtKWv4PyazQARFLnrV/qA4qlAK008= go.ipao.vip/atom v1.2.1/go.mod h1:woAv+rZf0xd+7mEtKWv4PyazQARFLnrV/qA4qlAK008=
go.ipao.vip/atom v1.3.1 h1:tOh5OBH3vbnNsINhvnesw5kxY9g82hQ1AkeExUUU/+A=
go.ipao.vip/atom v1.3.1/go.mod h1:woAv+rZf0xd+7mEtKWv4PyazQARFLnrV/qA4qlAK008=
go.ipao.vip/gen v0.0.0-20250924024520-70c4accdea44 h1:i7zFEsfUYRJQo0mXUWI/RoEkgEdTNmLt0Io2rwhqY9E= go.ipao.vip/gen v0.0.0-20250924024520-70c4accdea44 h1:i7zFEsfUYRJQo0mXUWI/RoEkgEdTNmLt0Io2rwhqY9E=
go.ipao.vip/gen v0.0.0-20250924024520-70c4accdea44/go.mod h1:ip5X9ioxR9hvM/mrsA77KWXFsrMm5oki5rfY5MSkssM= go.ipao.vip/gen v0.0.0-20250924024520-70c4accdea44/go.mod h1:ip5X9ioxR9hvM/mrsA77KWXFsrMm5oki5rfY5MSkssM=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=

View File

@@ -1,11 +0,0 @@
import client from './client';
export const wechatApi = {
jsSdk() {
return client.get('/wechats/js-sdk', {
params: {
url: window.location.href.split('#')[0],
},
});
},
}

View File

@@ -1,6 +1,8 @@
import wx from "weixin-js-sdk"; import wx from "weixin-js-sdk";
export function useWxSDK() { export function useWxSDK() {
let ready = false;
/** /**
* 初始化设置 * 初始化设置
*/ */
@@ -24,8 +26,13 @@ export function useWxSDK() {
}); });
wx.ready(() => { wx.ready(() => {
console.log("wx.ready called"); console.log("wx.ready called");
ready = true;
resolve(true); resolve(true);
}); });
wx.error(() => {
ready = false;
resolve(false);
});
}); });
} }
@@ -35,39 +42,46 @@ export function useWxSDK() {
onSuccess = () => { }, onSuccess = () => { },
onCancel = () => { } onCancel = () => { }
) { ) {
if (!ready) {
return;
}
console.log("setShareInfo called", shareInfo); console.log("setShareInfo called", shareInfo);
wx.updateTimelineShareData({ try {
title: shareInfo.title, // 分享标题 wx.updateTimelineShareData({
link: shareInfo.link, // 分享链接可以不是当前页面该链接域名或路径必须与当前页面对应的公众号JS安全域名一致 title: shareInfo.title, // 分享标题
imgUrl: shareInfo.imgUrl, link: shareInfo.link, // 分享链接可以不是当前页面该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
success: function (e) { imgUrl: shareInfo.imgUrl,
console.log("分享朋友圈成功", e); success: function (e) {
// 用户确认分享后执行的回调函数 console.log("分享朋友圈成功", e);
onSuccess(); // 用户确认分享后执行的回调函数
}, onSuccess();
cancel: function (e) { },
console.log("分享朋友圈取消", e); cancel: function (e) {
onCancel(); console.log("分享朋友圈取消", e);
// 用户取消分享后执行的回调函数 onCancel();
}, // 用户取消分享后执行的回调函数
}); },
wx.updateAppMessageShareData({ });
title: shareInfo.title, // 分享标题 wx.updateAppMessageShareData({
desc: shareInfo.desc, title: shareInfo.title, // 分享标题
link: shareInfo.link, // 分享链接可以不是当前页面该链接域名或路径必须与当前页面对应的公众号JS安全域名一致 desc: shareInfo.desc,
imgUrl: shareInfo.imgUrl, link: shareInfo.link, // 分享链接可以不是当前页面该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
type: "link", // 分享类型,music、video或link不填默认为link imgUrl: shareInfo.imgUrl,
success: function (e) { type: "link", // 分享类型,music、video或link不填默认为link
// 用户确认分享后执行的回调函数 success: function (e) {
console.log("分享成功", e); // 用户确认分享后执行的回调函数
onSuccess(); console.log("分享成功", e);
}, onSuccess();
cancel: function (e) { },
// 用户取消分享后执行的回调函数 cancel: function (e) {
console.log("分享取消", e); // 用户取消分享后执行的回调函数
onCancel(); console.log("分享取消", e);
}, onCancel();
}); },
});
} catch {
// ignore
}
} }
/** 是否是ios微信 */ /** 是否是ios微信 */

View File

@@ -9,7 +9,6 @@ import { onMounted, onUnmounted, ref } from "vue";
import { BsChevronLeft } from "vue-icons-plus/bs"; import { BsChevronLeft } from "vue-icons-plus/bs";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { postApi } from "../api/postApi"; import { postApi } from "../api/postApi";
import { wechatApi } from "../api/wechatApi";
import { useWxSDK } from "../hooks/useWxSDK"; import { useWxSDK } from "../hooks/useWxSDK";
const wx = useWxSDK(); const wx = useWxSDK();
@@ -172,25 +171,6 @@ const handleBack = () => {
}; };
onMounted(async () => { onMounted(async () => {
wechatApi
.jsSdk()
.then((resp) => {
wx.initConfig(resp.data).then(() => {
wx.setShareInfo({
title: article.value.title,
desc: article.value.content,
link: window.location.href,
imgUrl: article.value.head_images[0],
});
});
})
.catch((error) => {
console.error("Failed to initialize WeChat SDK:", error);
})
.finally(() => {
});
await fetchArticle(); await fetchArticle();
}); });

File diff suppressed because one or more lines are too long