From fe8aaff92ec6ec6df3e998499521c123b3e604c0 Mon Sep 17 00:00:00 2001 From: ctwj <908504609@qq.com> Date: Wed, 12 Nov 2025 00:57:41 +0800 Subject: [PATCH] update: seo --- go.mod | 18 ++- go.sum | 48 +++++--- handlers/og_image.go | 243 ++++++++++++++++++++++++++++++++++++++ main.go | 6 + web/.env.example | 5 + web/composables/useSeo.ts | 151 +++++++++++++++++++++-- web/nuxt.config.ts | 18 ++- web/pages/admin/bot.vue | 10 -- web/pages/api-docs.vue | 38 +++++- web/pages/hot-dramas.vue | 38 +++++- web/pages/index.vue | 59 +++++++-- 11 files changed, 563 insertions(+), 71 deletions(-) create mode 100644 handlers/og_image.go create mode 100644 web/.env.example diff --git a/go.mod b/go.mod index e02358c..3159425 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,9 @@ module github.com/ctwj/urldb -go 1.23.0 - -toolchain go1.23.3 +go 1.24.0 require ( + github.com/fogleman/gg v1.3.0 github.com/gin-contrib/cors v1.4.0 github.com/gin-gonic/gin v1.10.1 github.com/go-resty/resty/v2 v2.16.5 @@ -12,7 +11,9 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.0 github.com/joho/godotenv v1.5.1 github.com/meilisearch/meilisearch-go v0.33.1 + github.com/prometheus/client_golang v1.23.2 github.com/robfig/cron/v3 v3.0.1 + github.com/silenceper/wechat/v2 v2.1.10 golang.org/x/crypto v0.41.0 gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.30.0 @@ -24,21 +25,19 @@ require ( github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/fatih/structs v1.1.0 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect - github.com/silenceper/wechat/v2 v2.1.10 // indirect github.com/sirupsen/logrus v1.9.0 // indirect - github.com/spf13/cast v1.4.1 // indirect github.com/tidwall/gjson v1.14.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/image v0.32.0 // indirect ) require ( @@ -59,7 +58,6 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/matoous/go-nanoid/v2 v2.1.0 github.com/mattn/go-isatty v0.0.20 // indirect @@ -71,9 +69,9 @@ require ( github.com/ugorji/go/codec v1.3.0 // indirect golang.org/x/arch v0.19.0 // indirect golang.org/x/net v0.43.0 - golang.org/x/sync v0.16.0 // indirect + golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/text v0.30.0 // indirect google.golang.org/protobuf v1.36.8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 34c498f..c4bf1d4 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M= github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= @@ -26,9 +28,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= @@ -66,6 +70,8 @@ github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXe github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -83,6 +89,7 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -102,6 +109,8 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= @@ -115,6 +124,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= @@ -134,15 +145,18 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -162,14 +176,12 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/silenceper/wechat/v2 v2.1.10 h1:jMg0//CZBIuogEvuXgxJQuJ47SsPPAqFrrbOtro2pko= github.com/silenceper/wechat/v2 v2.1.10/go.mod h1:7Iu3EhQYVtDUJAj+ZVRy8yom75ga7aDWv8RurLkVm0s= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -182,8 +194,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -199,7 +211,10 @@ github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2W github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ= github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU= @@ -209,10 +224,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ= +golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -222,15 +237,13 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -250,18 +263,14 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -280,8 +289,6 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -290,11 +297,14 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handlers/og_image.go b/handlers/og_image.go new file mode 100644 index 0000000..35efae2 --- /dev/null +++ b/handlers/og_image.go @@ -0,0 +1,243 @@ +package handlers + +import ( + "bytes" + "net/http" + "strconv" + "strings" + + "github.com/ctwj/urldb/utils" + "github.com/gin-gonic/gin" + "github.com/fogleman/gg" + "image/color" +) + +// OGImageHandler 处理OG图片生成请求 +type OGImageHandler struct{} + +// NewOGImageHandler 创建新的OG图片处理器 +func NewOGImageHandler() *OGImageHandler { + return &OGImageHandler{} +} + +// GenerateOGImage 生成OG图片 +func (h *OGImageHandler) GenerateOGImage(c *gin.Context) { + // 获取请求参数 + title := strings.TrimSpace(c.Query("title")) + description := strings.TrimSpace(c.Query("description")) + siteName := strings.TrimSpace(c.Query("site_name")) + theme := strings.TrimSpace(c.Query("theme")) + + width, _ := strconv.Atoi(c.Query("width")) + height, _ := strconv.Atoi(c.Query("height")) + + // 设置默认值 + if title == "" { + title = "老九网盘资源数据库" + } + if siteName == "" { + siteName = "老九网盘" + } + if width <= 0 || width > 2000 { + width = 1200 + } + if height <= 0 || height > 2000 { + height = 630 + } + + // 生成图片 + imageBuffer, err := createOGImage(title, description, siteName, theme, width, height) + if err != nil { + utils.Error("生成OG图片失败: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to generate image: " + err.Error(), + }) + return + } + + // 返回图片 + c.Data(http.StatusOK, "image/png", imageBuffer.Bytes()) + c.Header("Content-Type", "image/png") + c.Header("Cache-Control", "public, max-age=3600") +} + +// createOGImage 创建OG图片 +func createOGImage(title, description, siteName, theme string, width, height int) (*bytes.Buffer, error) { + dc := gg.NewContext(width, height) + + // 设置背景色 + bgColor := getBackgroundColor(theme) + dc.SetColor(bgColor) + dc.DrawRectangle(0, 0, float64(width), float64(height)) + dc.Fill() + + // 绘制渐变效果 + gradient := gg.NewLinearGradient(0, 0, float64(width), float64(height)) + gradient.AddColorStop(0, getGradientStartColor(theme)) + gradient.AddColorStop(1, getGradientEndColor(theme)) + dc.SetFillStyle(gradient) + dc.DrawRectangle(0, 0, float64(width), float64(height)) + dc.Fill() + + // 设置站点标识 + dc.SetHexColor("#ffffff") + // 尝试加载字体,如果失败则使用默认字体 + if err := dc.LoadFontFace("assets/fonts/SourceHanSansCN-Regular.ttc", 24); err != nil { + // 使用默认字体设置 + } + + dc.DrawStringAnchored(siteName, 60, 50, 0, 0.5) + + // 绘制标题 + dc.SetHexColor("#ffffff") + if err := dc.LoadFontFace("assets/fonts/SourceHanSansCN-Bold.ttc", 48); err != nil { + // 使用默认字体设置 + } + + // 文字居中处理 + titleWidth, _ := dc.MeasureString(title) + if titleWidth > float64(width-120) { + // 如果标题过长,尝试加载较小字体 + if err := dc.LoadFontFace("assets/fonts/SourceHanSansCN-Bold.ttc", 42); err != nil { + // 使用默认字体设置 + } + titleWidth, _ = dc.MeasureString(title) + if titleWidth > float64(width-120) { + if err := dc.LoadFontFace("assets/fonts/SourceHanSansCN-Bold.ttc", 36); err != nil { + // 使用默认字体设置 + } + } + } + + dc.DrawStringAnchored(title, float64(width)/2, float64(height)/2-30, 0.5, 0.5) + + // 绘制描述 + if description != "" { + dc.SetHexColor("#e5e7eb") + // 尝试加载较小字体 + if err := dc.LoadFontFace("assets/fonts/SourceHanSansCN-Regular.ttc", 28); err != nil { + // 使用默认字体设置 + } + + // 自动换行处理 + wrappedDesc := wrapText(dc, description, float64(width-120)) + startY := float64(height)/2 + 40 + + for i, line := range wrappedDesc { + y := startY + float64(i)*35 + dc.DrawStringAnchored(line, float64(width)/2, y, 0.5, 0.5) + } + } + + // 添加装饰性元素 + drawDecorativeElements(dc, width, height, theme) + + // 生成图片 + buf := &bytes.Buffer{} + err := dc.EncodePNG(buf) + if err != nil { + return nil, err + } + + return buf, nil +} + +// getBackgroundColor 获取背景色 +func getBackgroundColor(theme string) color.RGBA { + switch theme { + case "dark": + return color.RGBA{31, 41, 55, 255} // slate-800 + case "blue": + return color.RGBA{29, 78, 216, 255} // blue-700 + case "green": + return color.RGBA{6, 95, 70, 255} // emerald-800 + case "purple": + return color.RGBA{109, 40, 217, 255} // violet-700 + default: + return color.RGBA{55, 65, 81, 255} // gray-800 + } +} + +// getGradientStartColor 获取渐变起始色 +func getGradientStartColor(theme string) color.Color { + switch theme { + case "dark": + return color.RGBA{15, 23, 42, 255} // slate-900 + case "blue": + return color.RGBA{30, 58, 138, 255} // blue-900 + case "green": + return color.RGBA{6, 78, 59, 255} // emerald-900 + case "purple": + return color.RGBA{91, 33, 182, 255} // violet-800 + default: + return color.RGBA{31, 41, 55, 255} // gray-800 + } +} + +// getGradientEndColor 获取渐变结束色 +func getGradientEndColor(theme string) color.Color { + switch theme { + case "dark": + return color.RGBA{55, 65, 81, 255} // slate-700 + case "blue": + return color.RGBA{59, 130, 246, 255} // blue-500 + case "green": + return color.RGBA{16, 185, 129, 255} // emerald-500 + case "purple": + return color.RGBA{139, 92, 246, 255} // violet-500 + default: + return color.RGBA{75, 85, 99, 255} // gray-600 + } +} + +// wrapText 文本自动换行处理 +func wrapText(dc *gg.Context, text string, maxWidth float64) []string { + var lines []string + words := []rune(text) + + currentLine := "" + for _, word := range words { + testLine := currentLine + string(word) + width, _ := dc.MeasureString(testLine) + + if width > maxWidth && len(currentLine) > 0 { + lines = append(lines, currentLine) + currentLine = string(word) + } else { + currentLine = testLine + } + } + + if currentLine != "" { + lines = append(lines, currentLine) + } + + // 最多显示3行 + if len(lines) > 3 { + lines = lines[:3] + // 在最后一行添加省略号 + if len(lines[2]) > 3 { + lines[2] = lines[2][:len(lines[2])-3] + "..." + } + } + + return lines +} + +// drawDecorativeElements 绘制装饰性元素 +func drawDecorativeElements(dc *gg.Context, width, height int, theme string) { + // 绘制装饰性圆点 + dc.SetHexColor("#ffffff") + dc.SetLineWidth(2) + + for i := 0; i < 5; i++ { + x := float64(100 + i*150) + y := float64(100 + (i%2)*200) + dc.DrawCircle(x, y, 8) + dc.Stroke() + } + + // 绘制底部装饰线 + dc.DrawLine(60, float64(height-80), float64(width-60), float64(height-80)) + dc.Stroke() +} \ No newline at end of file diff --git a/main.go b/main.go index 91461ee..d7385d3 100644 --- a/main.go +++ b/main.go @@ -208,6 +208,9 @@ func main() { // 创建Meilisearch处理器 meilisearchHandler := handlers.NewMeilisearchHandler(meilisearchManager) + // 创建OG图片处理器 + ogImageHandler := handlers.NewOGImageHandler() + // API路由 api := r.Group("/api") { @@ -434,6 +437,9 @@ func main() { api.GET("/wechat/bot-status", middleware.AuthMiddleware(), middleware.AdminMiddleware(), wechatHandler.GetBotStatus) api.POST("/wechat/callback", wechatHandler.HandleWechatMessage) api.GET("/wechat/callback", wechatHandler.HandleWechatMessage) + + // OG图片生成路由 + api.GET("/og-image", ogImageHandler.GenerateOGImage) } // 设置监控系统 diff --git a/web/.env.example b/web/.env.example new file mode 100644 index 0000000..e2f92ae --- /dev/null +++ b/web/.env.example @@ -0,0 +1,5 @@ +# API Server Configuration +NUXT_PUBLIC_API_SERVER=http://localhost:8080/api + +# OG Image Service Configuration +NUXT_PUBLIC_OG_API_URL=http://localhost:8081/api/og-image \ No newline at end of file diff --git a/web/composables/useSeo.ts b/web/composables/useSeo.ts index 8880135..e70c57e 100644 --- a/web/composables/useSeo.ts +++ b/web/composables/useSeo.ts @@ -1,4 +1,5 @@ -import { ref, computed } from 'vue' +import { ref } from 'vue' +import { useRoute } from '#imports' interface SystemConfig { id: number @@ -58,20 +59,147 @@ export const useSeo = () => { } } - // 设置页面SEO - const setPageSeo = (pageTitle: string, customMeta?: Record) => { + // 生成动态OG图片URL + const generateOgImageUrl = (title: string, description?: string, theme: string = 'default') => { + // 获取运行时配置 + const config = useRuntimeConfig() + const ogApiUrl = config.public.ogApiUrl || '/api/og-image' + + // 构建URL参数 + const params = new URLSearchParams() + params.set('title', title) + + if (description) { + // 限制描述长度 + const trimmedDesc = description.length > 200 ? description.substring(0, 200) + '...' : description + params.set('description', trimmedDesc) + } + + params.set('site_name', systemConfig.value?.site_title || '老九网盘资源数据库') + params.set('theme', theme) + params.set('width', '1200') + params.set('height', '630') + + // 如果是相对路径,添加当前域名 + if (ogApiUrl.startsWith('/')) { + if (process.client) { + const origin = window.location.origin + return `${origin}${ogApiUrl}?${params.toString()}` + } + // 服务端渲染时使用配置的API基础URL + const apiBase = config.public.apiBase || 'http://localhost:8080' + return `${apiBase}${ogApiUrl}?${params.toString()}` + } + + return `${ogApiUrl}?${params.toString()}` + } + + // 生成动态SEO元数据 + const generateDynamicSeo = (pageTitle: string, customMeta?: Record, routeQuery?: Record) => { const title = generateTitle(pageTitle) const meta = generateMeta(customMeta) + const route = routeQuery || useRoute() + + // 根据路由参数生成动态描述 + const searchKeyword = route.query?.search as string || '' + const platformId = route.query?.platform as string || '' + + let dynamicDescription = meta.description + if (searchKeyword && platformId) { + dynamicDescription = `在${platformId}中搜索"${searchKeyword}"的相关资源。${meta.description}` + } else if (searchKeyword) { + dynamicDescription = `搜索"${searchKeyword}"的相关资源。${meta.description}` + } + + // 动态关键词 + let dynamicKeywords = meta.keywords + if (searchKeyword) { + dynamicKeywords = `${searchKeyword},${meta.keywords}` + } + + // 生成动态OG图片URL + const theme = searchKeyword ? 'blue' : platformId ? 'green' : 'default' + const ogImageUrl = generateOgImageUrl(title, dynamicDescription, theme) + + return { + title, + description: dynamicDescription, + keywords: dynamicKeywords, + ogTitle: title, + ogDescription: dynamicDescription, + ogType: 'website', + ogImage: ogImageUrl, + twitterCard: 'summary_large_image', + robots: 'index, follow' + } + } + + // 设置页面SEO - 使用Nuxt3最佳实践 + const setPageSeo = (pageTitle: string, customMeta?: Record, routeQuery?: Record) => { + const seoData = generateDynamicSeo(pageTitle, customMeta, routeQuery) + + useSeoMeta({ + title: seoData.title, + description: seoData.description, + keywords: seoData.keywords, + ogTitle: seoData.ogTitle, + ogDescription: seoData.ogDescription, + ogType: seoData.ogType, + ogImage: seoData.ogImage, + twitterCard: seoData.twitterCard, + robots: seoData.robots + }) + + // 设置canonical链接 + const baseUrl = 'https://yourdomain.com' // 应该从环境变量或配置中获取 + const params = new URLSearchParams() + if (routeQuery?.query?.search) params.set('search', routeQuery.query.search as string) + if (routeQuery?.query?.platform) params.set('platform', routeQuery.query.platform as string) + const queryString = params.toString() + const canonicalUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl useHead({ - title, - meta: [ - { name: 'description', content: meta.description }, - { name: 'keywords', content: meta.keywords }, - { name: 'author', content: meta.author }, - { name: 'copyright', content: meta.copyright } + htmlAttrs: { + lang: 'zh-CN' + }, + link: [ + { + rel: 'canonical', + href: canonicalUrl + } ] }) + + // 添加结构化数据 + useHead({ + script: [ + { + type: 'application/ld+json', + innerHTML: JSON.stringify({ + "@context": "https://schema.org", + "@type": "WebSite", + "name": systemConfig.value?.site_title || '老九网盘资源数据库', + "description": seoData.description, + "url": canonicalUrl + }) + } + ] + }) + } + + // 设置服务端SEO(适用于不需要在客户端更新的元数据) + const setServerSeo = (pageTitle: string, customMeta?: Record) => { + if (import.meta.server) { + const title = generateTitle(pageTitle) + const meta = generateMeta(customMeta) + + useServerSeoMeta({ + title: title, + description: meta.description, + keywords: meta.keywords, + robots: 'index, follow' + }) + } } return { @@ -79,6 +207,9 @@ export const useSeo = () => { fetchSystemConfig, generateTitle, generateMeta, - setPageSeo + generateOgImageUrl, + generateDynamicSeo, + setPageSeo, + setServerSeo } } \ No newline at end of file diff --git a/web/nuxt.config.ts b/web/nuxt.config.ts index 2572e1e..2a9f7af 100644 --- a/web/nuxt.config.ts +++ b/web/nuxt.config.ts @@ -55,13 +55,23 @@ export default defineNuxtConfig({ app: { head: { title: '老九网盘资源数据库', + htmlAttrs: { + lang: 'zh-CN' + }, meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, - { name: 'description', content: '老九网盘资源管理数据庫,现代化的网盘资源数据库,支持多网盘自动化转存分享,支持百度网盘,阿里云盘,夸克网盘, 天翼云盘,迅雷云盘,123云盘,115网盘,UC网盘' } + { name: 'description', content: '老九网盘资源管理数据庫,现代化的网盘资源数据库,支持多网盘自动化转存分享,支持百度网盘,阿里云盘,夸克网盘, 天翼云盘,迅雷云盘,123云盘,115网盘,UC网盘' }, + { name: 'robots', content: 'index, follow' }, + { name: 'theme-color', content: '#3b82f6' }, + { property: 'og:site_name', content: '老九网盘资源数据库' }, + { property: 'og:type', content: 'website' }, + { name: 'twitter:card', content: 'summary_large_image' } ], link: [ - { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } + { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, + { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, + { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: 'anonymous' } ] } }, @@ -70,7 +80,9 @@ export default defineNuxtConfig({ // 客户端API地址:开发环境通过代理,生产环境通过Nginx apiBase: '/api', // 服务端API地址:通过环境变量配置,支持不同部署方式 - apiServer: process.env.NUXT_PUBLIC_API_SERVER || (process.env.NODE_ENV === 'production' ? 'http://backend:8080/api' : '/api') + apiServer: process.env.NUXT_PUBLIC_API_SERVER || (process.env.NODE_ENV === 'production' ? 'http://backend:8080/api' : '/api'), + // OG图片服务API地址(集成到主服务中) + ogApiUrl: process.env.NUXT_PUBLIC_OG_API_URL || (process.env.NODE_ENV === 'production' ? '/api/og-image' : '/api/og-image') } }, build: { diff --git a/web/pages/admin/bot.vue b/web/pages/admin/bot.vue index 196065f..1283e8a 100644 --- a/web/pages/admin/bot.vue +++ b/web/pages/admin/bot.vue @@ -31,16 +31,6 @@ - - -
-
- -

功能暂未开放

-

微信开放平台机器人功能正在开发中,敬请期待

-
-
-
diff --git a/web/pages/api-docs.vue b/web/pages/api-docs.vue index 0185416..25b68e8 100644 --- a/web/pages/api-docs.vue +++ b/web/pages/api-docs.vue @@ -459,12 +459,40 @@ definePageMeta({ layout: 'default' }) -// 页面元数据 -useHead({ +// 页面元数据 - 使用Nuxt3 SEO最佳实践 +useSeoMeta({ title: 'API文档 - 老九网盘资源数据库', - meta: [ - { name: 'description', content: '老九网盘资源数据库的公开API接口文档' }, - { name: 'keywords', content: 'API,接口文档,网盘资源管理' } + description: '老九网盘资源数据库的公开API接口文档,支持资源添加、搜索和热门剧获取等功能', + keywords: 'API,接口文档,网盘资源管理,资源搜索,批量添加', + ogTitle: 'API文档 - 老九网盘资源数据库', + ogDescription: '老九网盘资源数据库的公开API接口文档,支持资源添加、搜索和热门剧获取等功能', + ogType: 'website', + ogImage: '/assets/images/logo.webp', + twitterCard: 'summary_large_image', + robots: 'index, follow' +}) + +useHead({ + htmlAttrs: { + lang: 'zh-CN' + }, + link: [ + { + rel: 'canonical', + href: 'https://yourdomain.com/api-docs' + } + ], + script: [ + { + type: 'application/ld+json', + innerHTML: JSON.stringify({ + "@context": "https://schema.org", + "@type": "WebPage", + "name": "API文档 - 老九网盘资源数据库", + "description": "老九网盘资源数据库的公开API接口文档,支持资源添加、搜索和热门剧获取等功能", + "url": "https://yourdomain.com/api-docs" + }) + } ] }) diff --git a/web/pages/hot-dramas.vue b/web/pages/hot-dramas.vue index 02ed805..6059584 100644 --- a/web/pages/hot-dramas.vue +++ b/web/pages/hot-dramas.vue @@ -166,10 +166,46 @@ definePageMeta({ layout: 'default' }) +// 设置页面SEO元数据 - 使用Nuxt3 SEO最佳实践 +useSeoMeta({ + title: '热播剧榜单 - 老九网盘资源数据库', + description: '实时获取豆瓣热门电影和电视剧榜单,包括热门电影、热门电视剧、热门综艺和豆瓣Top250等分类', + keywords: '热播剧,热门电影,热门电视剧,豆瓣榜单,Top250,影视推荐', + ogTitle: '热播剧榜单 - 老九网盘资源数据库', + ogDescription: '实时获取豆瓣热门电影和电视剧榜单', + ogType: 'website', + ogImage: '/assets/images/logo.webp', + twitterCard: 'summary_large_image', + robots: 'index, follow' +}) + +useHead({ + htmlAttrs: { + lang: 'zh-CN' + }, + link: [ + { + rel: 'canonical', + href: 'https://yourdomain.com/hot-dramas' + } + ], + script: [ + { + type: 'application/ld+json', + innerHTML: JSON.stringify({ + "@context": "https://schema.org", + "@type": "WebPage", + "name": "热播剧榜单 - 老九网盘资源数据库", + "description": "实时获取豆瓣热门电影和电视剧榜单,包括热门电影、热门电视剧、热门综艺和豆瓣Top250等分类" + }) + } + ] +}) + const hotDramaApi = useHotDramaApi() const { data: hotDramsaResponse, error } = await hotDramaApi.getHotDramas({ page: 1, - page_size: 20 + page_size: 20 }) const { getPosterUrl } = hotDramaApi diff --git a/web/pages/index.vue b/web/pages/index.vue index 91753f1..e0f0a59 100644 --- a/web/pages/index.vue +++ b/web/pages/index.vue @@ -411,20 +411,53 @@ const pageKeywords = computed(() => { } }) -// 设置动态SEO - 修复useHead 500错误 -useHead(() => { - // 安全地获取标题,添加默认值 - const safeTitle = pageTitle.value || '老九网盘资源数据库 - 首页' - const safeDescription = pageDescription.value || '老九网盘资源管理系统, 一个现代化的网盘资源数据库,支持多网盘自动化转存分享' - const safeKeywords = pageKeywords.value || '网盘资源,资源管理,数据库' +// 设置动态SEO - 使用Nuxt3最佳实践 +useSeoMeta({ + title: () => pageTitle.value || '老九网盘资源数据库 - 首页', + description: () => pageDescription.value || '老九网盘资源管理系统, 一个现代化的网盘资源数据库,支持多网盘自动化转存分享', + keywords: () => pageKeywords.value || '网盘资源,资源管理,数据库', + ogTitle: () => pageTitle.value, + ogDescription: () => pageDescription.value, + ogType: 'website', + ogImage: '/assets/images/logo.webp', + twitterCard: 'summary_large_image', + robots: 'index, follow' +}) - return { - title: safeTitle, - meta: [ - { name: 'description', content: safeDescription }, - { name: 'keywords', content: safeKeywords } - ] - } +// 设置HTML属性 +useHead({ + htmlAttrs: { + lang: 'zh-CN' + }, + link: [ + { + rel: 'canonical', + href: () => { + const baseUrl = 'https://yourdomain.com' // 应该从环境变量或配置中获取 + const params = new URLSearchParams() + if (route.query.search) params.set('search', route.query.search as string) + if (route.query.platform) params.set('platform', route.query.platform as string) + const queryString = params.toString() + return queryString ? `${baseUrl}?${queryString}` : baseUrl + } + } + ] +}) + +// 添加结构化数据 +useHead({ + script: [ + { + type: 'application/ld+json', + innerHTML: () => JSON.stringify({ + "@context": "https://schema.org", + "@type": "WebSite", + "name": systemConfig.value?.site_title || '老九网盘资源数据库', + "description": systemConfig.value?.site_description || '老九网盘资源管理系统, 一个现代化的网盘资源数据库,支持多网盘自动化转存分享', + "url": "https://pan.l9.lc" // 应该从环境变量或配置中获取 + }) + } + ] }) // 响应式数据