数据结构优化
@@ -16,27 +16,38 @@ require (
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gammazero/toposort v0.1.1 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
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/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mozillazg/go-httpheader v0.2.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/qiniu/go-sdk/v7 v7.25.6 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.72 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
@@ -46,6 +57,8 @@ require (
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
modernc.org/fileutil v1.0.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4=
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
|
||||
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -13,16 +23,24 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gammazero/toposort v0.1.1 h1:OivGxsWxF3U3+U80VoLJ+f50HcPU1MIqE1JlKzoJ2Eg=
|
||||
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
@@ -32,13 +50,22 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
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/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
@@ -47,33 +74,60 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
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=
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260109033043-398149f17e54 h1:R0OB+D7w26BOVPdoOva58AgTKmJR8tK2KI15XeeLdG8=
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260109033043-398149f17e54/go.mod h1:+mNMTBuDMdEGhWzoQgc6kBdqeaQpWh5ba8zqmp2MxCU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
|
||||
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||
github.com/qiniu/go-sdk/v7 v7.25.6 h1:89KQX16Bv2x7MxhwpzWGGvQBOPIlGpAcnPQyfS3tRok=
|
||||
github.com/qiniu/go-sdk/v7 v7.25.6/go.mod h1:dmKtJ2ahhPWFVi9o1D5GemmWoh/ctuB9peqTowyTO8o=
|
||||
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
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/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
|
||||
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=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.72 h1:k9aD8ri7Sqy2hYGYo6I2+OslDgY6IT5R0jUOHHSjW5Y=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.72/go.mod h1:STbTNaNKq03u+gscPEGOahKzLcGSYOj6Dzc5zNay7Pg=
|
||||
github.com/tencentyun/qcloud-cos-sts-sdk v0.0.0-20250515025012-e0eec8a5d123/go.mod h1:b18KQa4IxHbxeseW1GcZox53d7J0z39VNONTxvvlkXw=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
@@ -82,25 +136,44 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
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/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
@@ -108,3 +181,5 @@ gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkD
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w=
|
||||
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
|
||||
|
||||
@@ -56,26 +56,169 @@ func AdminUploadAttachment(c *gin.Context) {
|
||||
}
|
||||
|
||||
if ossConfig != nil {
|
||||
// 解密AccessKey和SecretKey
|
||||
accessKey, err := utils.DecryptAES(ossConfig.AccessKey)
|
||||
if err != nil {
|
||||
log.Printf("Failed to decrypt access key: %v", err)
|
||||
utils.Error(c, 500, "Failed to decrypt OSS credentials")
|
||||
return
|
||||
}
|
||||
// 根据存储类型从专用字段或通用字段读取配置
|
||||
var accessKey, secretKey, bucket, region, domain string
|
||||
|
||||
secretKey, err := utils.DecryptAES(ossConfig.SecretKey)
|
||||
if err != nil {
|
||||
log.Printf("Failed to decrypt secret key: %v", err)
|
||||
utils.Error(c, 500, "Failed to decrypt OSS credentials")
|
||||
return
|
||||
switch ossConfig.StorageType {
|
||||
case "aliyun":
|
||||
// 优先使用专用字段
|
||||
if ossConfig.OSSAccessKeyID != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.OSSAccessKeyID)
|
||||
if err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if ossConfig.OSSAccessKeySecret != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.OSSAccessKeySecret)
|
||||
if err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
bucket = ossConfig.OSSBucket
|
||||
domain = ossConfig.OSSDomain
|
||||
// 如果专用字段为空,使用通用字段(向后兼容)
|
||||
if accessKey == "" && ossConfig.AccessKey != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.AccessKey)
|
||||
if err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if secretKey == "" && ossConfig.SecretKey != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.SecretKey)
|
||||
if err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = ossConfig.Bucket
|
||||
}
|
||||
if domain == "" {
|
||||
domain = ossConfig.Domain
|
||||
}
|
||||
case "qcloud":
|
||||
// 优先使用专用字段
|
||||
if ossConfig.QCloudSecretID != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.QCloudSecretID)
|
||||
if err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if ossConfig.QCloudSecretKey != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.QCloudSecretKey)
|
||||
if err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
bucket = ossConfig.QCloudBucket
|
||||
region = ossConfig.QCloudRegion
|
||||
domain = ossConfig.QCloudDomain
|
||||
// 向后兼容
|
||||
if accessKey == "" && ossConfig.AccessKey != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.AccessKey)
|
||||
if err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if secretKey == "" && ossConfig.SecretKey != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.SecretKey)
|
||||
if err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = ossConfig.Bucket
|
||||
}
|
||||
if region == "" {
|
||||
region = ossConfig.Region
|
||||
}
|
||||
if domain == "" {
|
||||
domain = ossConfig.Domain
|
||||
}
|
||||
case "qiniu":
|
||||
// 优先使用专用字段
|
||||
if ossConfig.QiniuAccessKey != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.QiniuAccessKey)
|
||||
if err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if ossConfig.QiniuSecretKey != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.QiniuSecretKey)
|
||||
if err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
bucket = ossConfig.QiniuBucket
|
||||
region = ossConfig.QiniuRegion
|
||||
domain = ossConfig.QiniuDomain
|
||||
// 向后兼容
|
||||
if accessKey == "" && ossConfig.AccessKey != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.AccessKey)
|
||||
if err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if secretKey == "" && ossConfig.SecretKey != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.SecretKey)
|
||||
if err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = ossConfig.Bucket
|
||||
}
|
||||
if region == "" {
|
||||
region = ossConfig.Region
|
||||
}
|
||||
if domain == "" {
|
||||
domain = ossConfig.Domain
|
||||
}
|
||||
default:
|
||||
// 使用通用字段
|
||||
if ossConfig.AccessKey != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.AccessKey)
|
||||
if err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if ossConfig.SecretKey != "" {
|
||||
decrypted, err := utils.DecryptAES(ossConfig.SecretKey)
|
||||
if err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
bucket = ossConfig.Bucket
|
||||
region = ossConfig.Region
|
||||
domain = ossConfig.Domain
|
||||
}
|
||||
|
||||
config.AccessKey = accessKey
|
||||
config.SecretKey = secretKey
|
||||
config.Bucket = ossConfig.Bucket
|
||||
config.Region = ossConfig.Region
|
||||
config.Domain = ossConfig.Domain
|
||||
config.Bucket = bucket
|
||||
config.Region = region
|
||||
config.Domain = domain
|
||||
|
||||
// 填充专用字段
|
||||
switch ossConfig.StorageType {
|
||||
case "aliyun":
|
||||
config.OSSAccessKeyID = accessKey
|
||||
config.OSSAccessKeySecret = secretKey
|
||||
config.OSSEndpoint = ossConfig.OSSEndpoint
|
||||
config.OSSBucket = bucket
|
||||
config.OSSDomain = domain
|
||||
case "qcloud":
|
||||
config.QCloudSecretID = accessKey
|
||||
config.QCloudSecretKey = secretKey
|
||||
config.QCloudRegion = region
|
||||
config.QCloudBucket = bucket
|
||||
config.QCloudDomain = domain
|
||||
case "qiniu":
|
||||
config.QiniuAccessKey = accessKey
|
||||
config.QiniuSecretKey = secretKey
|
||||
config.QiniuBucket = bucket
|
||||
config.QiniuRegion = region
|
||||
config.QiniuDomain = domain
|
||||
}
|
||||
}
|
||||
|
||||
// 获取上传器
|
||||
@@ -184,16 +327,129 @@ func AdminDeleteAttachment(c *gin.Context) {
|
||||
StorageType: attachment.StorageType,
|
||||
}
|
||||
|
||||
// 解密密钥
|
||||
if accessKey, err := utils.DecryptAES(ossConfig.AccessKey); err == nil {
|
||||
config.AccessKey = accessKey
|
||||
// 根据存储类型从专用字段或通用字段读取配置
|
||||
var accessKey, secretKey, bucket, region, domain string
|
||||
|
||||
switch ossConfig.StorageType {
|
||||
case "aliyun":
|
||||
if ossConfig.OSSAccessKeyID != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.OSSAccessKeyID); err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if ossConfig.OSSAccessKeySecret != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.OSSAccessKeySecret); err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
bucket = ossConfig.OSSBucket
|
||||
domain = ossConfig.OSSDomain
|
||||
// 向后兼容
|
||||
if accessKey == "" && ossConfig.AccessKey != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.AccessKey); err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if secretKey == "" && ossConfig.SecretKey != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.SecretKey); err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = ossConfig.Bucket
|
||||
}
|
||||
if domain == "" {
|
||||
domain = ossConfig.Domain
|
||||
}
|
||||
case "qcloud":
|
||||
if ossConfig.QCloudSecretID != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.QCloudSecretID); err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if ossConfig.QCloudSecretKey != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.QCloudSecretKey); err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
bucket = ossConfig.QCloudBucket
|
||||
region = ossConfig.QCloudRegion
|
||||
domain = ossConfig.QCloudDomain
|
||||
// 向后兼容
|
||||
if accessKey == "" && ossConfig.AccessKey != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.AccessKey); err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if secretKey == "" && ossConfig.SecretKey != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.SecretKey); err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = ossConfig.Bucket
|
||||
}
|
||||
if region == "" {
|
||||
region = ossConfig.Region
|
||||
}
|
||||
if domain == "" {
|
||||
domain = ossConfig.Domain
|
||||
}
|
||||
case "qiniu":
|
||||
if ossConfig.QiniuAccessKey != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.QiniuAccessKey); err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if ossConfig.QiniuSecretKey != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.QiniuSecretKey); err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
bucket = ossConfig.QiniuBucket
|
||||
region = ossConfig.QiniuRegion
|
||||
domain = ossConfig.QiniuDomain
|
||||
// 向后兼容
|
||||
if accessKey == "" && ossConfig.AccessKey != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.AccessKey); err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if secretKey == "" && ossConfig.SecretKey != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.SecretKey); err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = ossConfig.Bucket
|
||||
}
|
||||
if region == "" {
|
||||
region = ossConfig.Region
|
||||
}
|
||||
if domain == "" {
|
||||
domain = ossConfig.Domain
|
||||
}
|
||||
default:
|
||||
if ossConfig.AccessKey != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.AccessKey); err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if ossConfig.SecretKey != "" {
|
||||
if decrypted, err := utils.DecryptAES(ossConfig.SecretKey); err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
bucket = ossConfig.Bucket
|
||||
region = ossConfig.Region
|
||||
domain = ossConfig.Domain
|
||||
}
|
||||
if secretKey, err := utils.DecryptAES(ossConfig.SecretKey); err == nil {
|
||||
config.SecretKey = secretKey
|
||||
}
|
||||
config.Bucket = ossConfig.Bucket
|
||||
config.Region = ossConfig.Region
|
||||
config.Domain = ossConfig.Domain
|
||||
|
||||
config.AccessKey = accessKey
|
||||
config.SecretKey = secretKey
|
||||
config.Bucket = bucket
|
||||
config.Region = region
|
||||
config.Domain = domain
|
||||
|
||||
uploader, err := utils.GetOSSUploader(config)
|
||||
if err == nil {
|
||||
@@ -211,6 +467,58 @@ func AdminDeleteAttachment(c *gin.Context) {
|
||||
utils.SuccessWithMsg(c, "Attachment deleted successfully", nil)
|
||||
}
|
||||
|
||||
// AdminUpdateAttachment 更新附件
|
||||
func AdminUpdateAttachment(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
var id uint
|
||||
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
|
||||
utils.Error(c, 400, "Invalid attachment ID")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取现有附件
|
||||
attachment, err := repositories.GetAttachmentByID(id)
|
||||
if err != nil || attachment == nil {
|
||||
utils.Error(c, 404, "Attachment not found")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析请求体
|
||||
var req struct {
|
||||
CategoryID *uint `json:"categoryId"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.Error(c, 400, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
// 更新分类ID
|
||||
if req.CategoryID != nil {
|
||||
if *req.CategoryID == 0 {
|
||||
// 如果categoryId为0,设置为nil(无分类)
|
||||
attachment.CategoryID = nil
|
||||
} else {
|
||||
attachment.CategoryID = req.CategoryID
|
||||
}
|
||||
}
|
||||
|
||||
// 更新附件
|
||||
if err := repositories.UpdateAttachment(attachment); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 重新获取更新后的附件(包含关联的分类信息)
|
||||
updatedAttachment, err := repositories.GetAttachmentByID(id)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Attachment updated successfully", updatedAttachment)
|
||||
}
|
||||
|
||||
// AdminGetAttachmentCategories 获取附件分类列表
|
||||
func AdminGetAttachmentCategories(c *gin.Context) {
|
||||
categories, err := repositories.GetAttachmentCategories()
|
||||
@@ -282,6 +590,29 @@ func AdminDeleteAttachmentCategory(c *gin.Context) {
|
||||
|
||||
// AdminGetOSSConfigs 获取OSS配置列表
|
||||
func AdminGetOSSConfigs(c *gin.Context) {
|
||||
// 检查是否存在默认本地存储配置,如果不存在则创建
|
||||
defaultConfig, err := repositories.GetDefaultLocalOSSConfig()
|
||||
if err != nil {
|
||||
log.Printf("Error checking default local OSS config: %v", err)
|
||||
} else if defaultConfig == nil {
|
||||
// 创建默认本地存储配置
|
||||
// 本地存储不需要AccessKey和SecretKey,直接使用空字符串(不加密)
|
||||
defaultOSSConfig := &models.OSSConfig{
|
||||
Name: "本地存储",
|
||||
StorageType: "local",
|
||||
AccessKey: "", // 空字符串,不加密
|
||||
SecretKey: "", // 空字符串,不加密
|
||||
Bucket: "",
|
||||
Region: "",
|
||||
Domain: "",
|
||||
IsActive: 1,
|
||||
}
|
||||
|
||||
if err := repositories.CreateOSSConfig(defaultOSSConfig); err != nil {
|
||||
log.Printf("Error creating default local OSS config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
configs, err := repositories.GetOSSConfigs()
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
@@ -292,6 +623,12 @@ func AdminGetOSSConfigs(c *gin.Context) {
|
||||
for i := range configs {
|
||||
configs[i].AccessKey = "***"
|
||||
configs[i].SecretKey = "***"
|
||||
configs[i].OSSAccessKeyID = "***"
|
||||
configs[i].OSSAccessKeySecret = "***"
|
||||
configs[i].QCloudSecretID = "***"
|
||||
configs[i].QCloudSecretKey = "***"
|
||||
configs[i].QiniuAccessKey = "***"
|
||||
configs[i].QiniuSecretKey = "***"
|
||||
}
|
||||
|
||||
utils.Success(c, configs)
|
||||
@@ -302,12 +639,31 @@ func AdminCreateOSSConfig(c *gin.Context) {
|
||||
var req struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
StorageType string `json:"storageType" binding:"required"`
|
||||
AccessKey string `json:"accessKey" binding:"required"`
|
||||
SecretKey string `json:"secretKey" binding:"required"`
|
||||
Bucket string `json:"bucket"`
|
||||
Region string `json:"region"`
|
||||
Domain string `json:"domain"`
|
||||
IsActive int `json:"isActive"`
|
||||
// 通用字段(向后兼容)
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Bucket string `json:"bucket"`
|
||||
Region string `json:"region"`
|
||||
Domain string `json:"domain"`
|
||||
// 阿里云OSS专用字段
|
||||
OSSAccessKeyID string `json:"ossAccessKeyId"`
|
||||
OSSAccessKeySecret string `json:"ossAccessKeySecret"`
|
||||
OSSEndpoint string `json:"ossEndpoint"`
|
||||
OSSBucket string `json:"ossBucket"`
|
||||
OSSDomain string `json:"ossDomain"`
|
||||
// 腾讯云COS专用字段
|
||||
QCloudSecretID string `json:"qcloudSecretId"`
|
||||
QCloudSecretKey string `json:"qcloudSecretKey"`
|
||||
QCloudRegion string `json:"qcloudRegion"`
|
||||
QCloudBucket string `json:"qcloudBucket"`
|
||||
QCloudDomain string `json:"qcloudDomain"`
|
||||
// 七牛云专用字段
|
||||
QiniuAccessKey string `json:"qiniuAccessKey"`
|
||||
QiniuSecretKey string `json:"qiniuSecretKey"`
|
||||
QiniuBucket string `json:"qiniuBucket"`
|
||||
QiniuRegion string `json:"qiniuRegion"`
|
||||
QiniuDomain string `json:"qiniuDomain"`
|
||||
IsActive int `json:"isActive"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@@ -315,28 +671,90 @@ func AdminCreateOSSConfig(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 加密密钥
|
||||
encryptedAccessKey, err := utils.EncryptAES(req.AccessKey)
|
||||
// 加密函数
|
||||
encryptKey := func(key string) (string, error) {
|
||||
if key == "" {
|
||||
return "", nil
|
||||
}
|
||||
return utils.EncryptAES(key)
|
||||
}
|
||||
|
||||
// 加密通用密钥(向后兼容)
|
||||
encryptedAccessKey, err := encryptKey(req.AccessKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt access key")
|
||||
utils.Error(c, 500, "Failed to encrypt access key: "+err.Error())
|
||||
return
|
||||
}
|
||||
encryptedSecretKey, err := encryptKey(req.SecretKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt secret key: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
encryptedSecretKey, err := utils.EncryptAES(req.SecretKey)
|
||||
// 加密阿里云密钥
|
||||
encryptedOSSAccessKeyID, err := encryptKey(req.OSSAccessKeyID)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt secret key")
|
||||
utils.Error(c, 500, "Failed to encrypt OSS access key ID: "+err.Error())
|
||||
return
|
||||
}
|
||||
encryptedOSSAccessKeySecret, err := encryptKey(req.OSSAccessKeySecret)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt OSS access key secret: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 加密腾讯云密钥
|
||||
encryptedQCloudSecretID, err := encryptKey(req.QCloudSecretID)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt QCloud secret ID: "+err.Error())
|
||||
return
|
||||
}
|
||||
encryptedQCloudSecretKey, err := encryptKey(req.QCloudSecretKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt QCloud secret key: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 加密七牛云密钥
|
||||
encryptedQiniuAccessKey, err := encryptKey(req.QiniuAccessKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt Qiniu access key: "+err.Error())
|
||||
return
|
||||
}
|
||||
encryptedQiniuSecretKey, err := encryptKey(req.QiniuSecretKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt Qiniu secret key: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ossConfig := &models.OSSConfig{
|
||||
Name: req.Name,
|
||||
StorageType: req.StorageType,
|
||||
AccessKey: encryptedAccessKey,
|
||||
SecretKey: encryptedSecretKey,
|
||||
Bucket: req.Bucket,
|
||||
Region: req.Region,
|
||||
Domain: req.Domain,
|
||||
IsActive: req.IsActive,
|
||||
// 通用字段(向后兼容)
|
||||
AccessKey: encryptedAccessKey,
|
||||
SecretKey: encryptedSecretKey,
|
||||
Bucket: req.Bucket,
|
||||
Region: req.Region,
|
||||
Domain: req.Domain,
|
||||
// 阿里云OSS专用字段
|
||||
OSSAccessKeyID: encryptedOSSAccessKeyID,
|
||||
OSSAccessKeySecret: encryptedOSSAccessKeySecret,
|
||||
OSSEndpoint: req.OSSEndpoint,
|
||||
OSSBucket: req.OSSBucket,
|
||||
OSSDomain: req.OSSDomain,
|
||||
// 腾讯云COS专用字段
|
||||
QCloudSecretID: encryptedQCloudSecretID,
|
||||
QCloudSecretKey: encryptedQCloudSecretKey,
|
||||
QCloudRegion: req.QCloudRegion,
|
||||
QCloudBucket: req.QCloudBucket,
|
||||
QCloudDomain: req.QCloudDomain,
|
||||
// 七牛云专用字段
|
||||
QiniuAccessKey: encryptedQiniuAccessKey,
|
||||
QiniuSecretKey: encryptedQiniuSecretKey,
|
||||
QiniuBucket: req.QiniuBucket,
|
||||
QiniuRegion: req.QiniuRegion,
|
||||
QiniuDomain: req.QiniuDomain,
|
||||
IsActive: req.IsActive,
|
||||
}
|
||||
|
||||
if err := repositories.CreateOSSConfig(ossConfig); err != nil {
|
||||
@@ -347,6 +765,12 @@ func AdminCreateOSSConfig(c *gin.Context) {
|
||||
// 不返回加密的密钥
|
||||
ossConfig.AccessKey = "***"
|
||||
ossConfig.SecretKey = "***"
|
||||
ossConfig.OSSAccessKeyID = "***"
|
||||
ossConfig.OSSAccessKeySecret = "***"
|
||||
ossConfig.QCloudSecretID = "***"
|
||||
ossConfig.QCloudSecretKey = "***"
|
||||
ossConfig.QiniuAccessKey = "***"
|
||||
ossConfig.QiniuSecretKey = "***"
|
||||
|
||||
utils.SuccessWithMsg(c, "OSS config created successfully", ossConfig)
|
||||
}
|
||||
@@ -363,12 +787,31 @@ func AdminUpdateOSSConfig(c *gin.Context) {
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
StorageType string `json:"storageType"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Bucket string `json:"bucket"`
|
||||
Region string `json:"region"`
|
||||
Domain string `json:"domain"`
|
||||
IsActive int `json:"isActive"`
|
||||
// 通用字段(向后兼容)
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Bucket string `json:"bucket"`
|
||||
Region string `json:"region"`
|
||||
Domain string `json:"domain"`
|
||||
// 阿里云OSS专用字段
|
||||
OSSAccessKeyID string `json:"ossAccessKeyId"`
|
||||
OSSAccessKeySecret string `json:"ossAccessKeySecret"`
|
||||
OSSEndpoint string `json:"ossEndpoint"`
|
||||
OSSBucket string `json:"ossBucket"`
|
||||
OSSDomain string `json:"ossDomain"`
|
||||
// 腾讯云COS专用字段
|
||||
QCloudSecretID string `json:"qcloudSecretId"`
|
||||
QCloudSecretKey string `json:"qcloudSecretKey"`
|
||||
QCloudRegion string `json:"qcloudRegion"`
|
||||
QCloudBucket string `json:"qcloudBucket"`
|
||||
QCloudDomain string `json:"qcloudDomain"`
|
||||
// 七牛云专用字段
|
||||
QiniuAccessKey string `json:"qiniuAccessKey"`
|
||||
QiniuSecretKey string `json:"qiniuSecretKey"`
|
||||
QiniuBucket string `json:"qiniuBucket"`
|
||||
QiniuRegion string `json:"qiniuRegion"`
|
||||
QiniuDomain string `json:"qiniuDomain"`
|
||||
IsActive int `json:"isActive"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@@ -383,6 +826,46 @@ func AdminUpdateOSSConfig(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否要启用配置,如果是非local类型,需要验证AccessKey和SecretKey
|
||||
if req.IsActive == 1 && existingConfig.IsActive == 0 {
|
||||
// 检查当前配置的AccessKey和SecretKey(需要解密)
|
||||
var accessKey, secretKey string
|
||||
if existingConfig.AccessKey != "" {
|
||||
decrypted, err := utils.DecryptAES(existingConfig.AccessKey)
|
||||
if err == nil {
|
||||
accessKey = decrypted
|
||||
}
|
||||
}
|
||||
if existingConfig.SecretKey != "" {
|
||||
decrypted, err := utils.DecryptAES(existingConfig.SecretKey)
|
||||
if err == nil {
|
||||
secretKey = decrypted
|
||||
}
|
||||
}
|
||||
|
||||
// 如果提供了新的AccessKey或SecretKey,使用新的
|
||||
if req.AccessKey != "" && req.AccessKey != "***" {
|
||||
accessKey = req.AccessKey
|
||||
}
|
||||
if req.SecretKey != "" && req.SecretKey != "***" {
|
||||
secretKey = req.SecretKey
|
||||
}
|
||||
|
||||
// 对于非local类型,必须填写AccessKey和SecretKey
|
||||
if existingConfig.StorageType != "local" && (accessKey == "" || secretKey == "") {
|
||||
utils.Error(c, 400, "启用非本地存储配置前必须填写AccessKey和SecretKey")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 加密函数
|
||||
encryptKey := func(key string) (string, error) {
|
||||
if key == "" {
|
||||
return "", nil
|
||||
}
|
||||
return utils.EncryptAES(key)
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if req.Name != "" {
|
||||
existingConfig.Name = req.Name
|
||||
@@ -390,31 +873,93 @@ func AdminUpdateOSSConfig(c *gin.Context) {
|
||||
if req.StorageType != "" {
|
||||
existingConfig.StorageType = req.StorageType
|
||||
}
|
||||
if req.AccessKey != "" && req.AccessKey != "***" {
|
||||
encryptedAccessKey, err := utils.EncryptAES(req.AccessKey)
|
||||
|
||||
// 处理通用字段(向后兼容)
|
||||
if req.AccessKey != "***" {
|
||||
encrypted, err := encryptKey(req.AccessKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt access key")
|
||||
utils.Error(c, 500, "Failed to encrypt access key: "+err.Error())
|
||||
return
|
||||
}
|
||||
existingConfig.AccessKey = encryptedAccessKey
|
||||
existingConfig.AccessKey = encrypted
|
||||
}
|
||||
if req.SecretKey != "" && req.SecretKey != "***" {
|
||||
encryptedSecretKey, err := utils.EncryptAES(req.SecretKey)
|
||||
if req.SecretKey != "***" {
|
||||
encrypted, err := encryptKey(req.SecretKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt secret key")
|
||||
utils.Error(c, 500, "Failed to encrypt secret key: "+err.Error())
|
||||
return
|
||||
}
|
||||
existingConfig.SecretKey = encryptedSecretKey
|
||||
existingConfig.SecretKey = encrypted
|
||||
}
|
||||
if req.Bucket != "" {
|
||||
existingConfig.Bucket = req.Bucket
|
||||
|
||||
// 更新通用字段(允许空字符串)
|
||||
existingConfig.Bucket = req.Bucket
|
||||
existingConfig.Region = req.Region
|
||||
existingConfig.Domain = req.Domain
|
||||
|
||||
// 处理阿里云OSS专用字段
|
||||
if req.OSSAccessKeyID != "***" {
|
||||
encrypted, err := encryptKey(req.OSSAccessKeyID)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt OSS access key ID: "+err.Error())
|
||||
return
|
||||
}
|
||||
existingConfig.OSSAccessKeyID = encrypted
|
||||
}
|
||||
if req.Region != "" {
|
||||
existingConfig.Region = req.Region
|
||||
if req.OSSAccessKeySecret != "***" {
|
||||
encrypted, err := encryptKey(req.OSSAccessKeySecret)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt OSS access key secret: "+err.Error())
|
||||
return
|
||||
}
|
||||
existingConfig.OSSAccessKeySecret = encrypted
|
||||
}
|
||||
if req.Domain != "" {
|
||||
existingConfig.Domain = req.Domain
|
||||
existingConfig.OSSEndpoint = req.OSSEndpoint
|
||||
existingConfig.OSSBucket = req.OSSBucket
|
||||
existingConfig.OSSDomain = req.OSSDomain
|
||||
|
||||
// 处理腾讯云COS专用字段
|
||||
if req.QCloudSecretID != "***" {
|
||||
encrypted, err := encryptKey(req.QCloudSecretID)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt QCloud secret ID: "+err.Error())
|
||||
return
|
||||
}
|
||||
existingConfig.QCloudSecretID = encrypted
|
||||
}
|
||||
if req.QCloudSecretKey != "***" {
|
||||
encrypted, err := encryptKey(req.QCloudSecretKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt QCloud secret key: "+err.Error())
|
||||
return
|
||||
}
|
||||
existingConfig.QCloudSecretKey = encrypted
|
||||
}
|
||||
existingConfig.QCloudRegion = req.QCloudRegion
|
||||
existingConfig.QCloudBucket = req.QCloudBucket
|
||||
existingConfig.QCloudDomain = req.QCloudDomain
|
||||
|
||||
// 处理七牛云专用字段
|
||||
if req.QiniuAccessKey != "***" {
|
||||
encrypted, err := encryptKey(req.QiniuAccessKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt Qiniu access key: "+err.Error())
|
||||
return
|
||||
}
|
||||
existingConfig.QiniuAccessKey = encrypted
|
||||
}
|
||||
if req.QiniuSecretKey != "***" {
|
||||
encrypted, err := encryptKey(req.QiniuSecretKey)
|
||||
if err != nil {
|
||||
utils.Error(c, 500, "Failed to encrypt Qiniu secret key: "+err.Error())
|
||||
return
|
||||
}
|
||||
existingConfig.QiniuSecretKey = encrypted
|
||||
}
|
||||
existingConfig.QiniuBucket = req.QiniuBucket
|
||||
existingConfig.QiniuRegion = req.QiniuRegion
|
||||
existingConfig.QiniuDomain = req.QiniuDomain
|
||||
|
||||
existingConfig.IsActive = req.IsActive
|
||||
|
||||
if err := repositories.UpdateOSSConfig(existingConfig); err != nil {
|
||||
@@ -425,6 +970,12 @@ func AdminUpdateOSSConfig(c *gin.Context) {
|
||||
// 不返回加密的密钥
|
||||
existingConfig.AccessKey = "***"
|
||||
existingConfig.SecretKey = "***"
|
||||
existingConfig.OSSAccessKeyID = "***"
|
||||
existingConfig.OSSAccessKeySecret = "***"
|
||||
existingConfig.QCloudSecretID = "***"
|
||||
existingConfig.QCloudSecretKey = "***"
|
||||
existingConfig.QiniuAccessKey = "***"
|
||||
existingConfig.QiniuSecretKey = "***"
|
||||
|
||||
utils.SuccessWithMsg(c, "OSS config updated successfully", existingConfig)
|
||||
}
|
||||
@@ -438,6 +989,24 @@ func AdminDeleteOSSConfig(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取配置信息,检查是否为默认本地存储配置
|
||||
ossConfig, err := repositories.GetOSSConfigByID(id)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if ossConfig == nil {
|
||||
utils.Error(c, 404, "OSS config not found")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否为默认本地存储配置,不允许删除
|
||||
if ossConfig.Name == "本地存储" && ossConfig.StorageType == "local" {
|
||||
utils.Error(c, 400, "默认本地存储配置不能删除")
|
||||
return
|
||||
}
|
||||
|
||||
if err := repositories.DeleteOSSConfig(id); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
@@ -76,36 +78,108 @@ func AdminGetWorks(c *gin.Context) {
|
||||
|
||||
// AdminCreateWork 创建作品
|
||||
func AdminCreateWork(c *gin.Context) {
|
||||
var work models.Work
|
||||
if err := c.ShouldBindJSON(&work); err != nil {
|
||||
var req struct {
|
||||
models.Work
|
||||
TechStack []map[string]interface{} `json:"techStack"`
|
||||
Gallery []string `json:"gallery"`
|
||||
Links map[string]interface{} `json:"links"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.Error(c, 400, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
if err := repositories.CreateWork(&work); err != nil {
|
||||
// 生成作品ID(如果没有提供)
|
||||
if req.Work.ID == "" {
|
||||
req.Work.ID = "work_" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
}
|
||||
|
||||
// 将links转换为JSON字符串
|
||||
if req.Links != nil {
|
||||
linksJSON, err := json.Marshal(req.Links)
|
||||
if err != nil {
|
||||
utils.Error(c, 400, "Invalid links format")
|
||||
return
|
||||
}
|
||||
req.Work.Links = string(linksJSON)
|
||||
}
|
||||
|
||||
// 创建作品
|
||||
if err := repositories.CreateWork(&req.Work); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Work created successfully", gin.H{"id": work.ID})
|
||||
// 保存技术栈
|
||||
if req.TechStack != nil && len(req.TechStack) > 0 {
|
||||
if err := repositories.CreateWorkTechStack(req.Work.ID, req.TechStack); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 保存图库
|
||||
if req.Gallery != nil && len(req.Gallery) > 0 {
|
||||
if err := repositories.CreateWorkGallery(req.Work.ID, req.Gallery); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Work created successfully", gin.H{"id": req.Work.ID})
|
||||
}
|
||||
|
||||
// AdminUpdateWork 更新作品
|
||||
func AdminUpdateWork(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var work models.Work
|
||||
if err := c.ShouldBindJSON(&work); err != nil {
|
||||
var req struct {
|
||||
models.Work
|
||||
TechStack []map[string]interface{} `json:"techStack"`
|
||||
Gallery []string `json:"gallery"`
|
||||
Links map[string]interface{} `json:"links"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.Error(c, 400, "Invalid request")
|
||||
return
|
||||
}
|
||||
work.ID = id
|
||||
|
||||
if err := repositories.UpdateWork(&work); err != nil {
|
||||
req.Work.ID = id
|
||||
|
||||
// 将links转换为JSON字符串
|
||||
if req.Links != nil {
|
||||
linksJSON, err := json.Marshal(req.Links)
|
||||
if err != nil {
|
||||
utils.Error(c, 400, "Invalid links format")
|
||||
return
|
||||
}
|
||||
req.Work.Links = string(linksJSON)
|
||||
}
|
||||
|
||||
// 更新作品
|
||||
if err := repositories.UpdateWork(&req.Work); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 更新技术栈(先删除旧的,再创建新的)
|
||||
if req.TechStack != nil {
|
||||
if err := repositories.CreateWorkTechStack(id, req.TechStack); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 更新图库(先删除旧的,再创建新的)
|
||||
if req.Gallery != nil {
|
||||
if err := repositories.CreateWorkGallery(id, req.Gallery); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
utils.SuccessWithMsg(c, "Work updated successfully", nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -197,6 +197,7 @@ func main() {
|
||||
// 附件管理 (复用 settings 权限)
|
||||
authAdmin.POST("/attachments/upload", middleware.PermissionMiddleware("settings", "create"), handlers.AdminUploadAttachment)
|
||||
authAdmin.GET("/attachments", middleware.PermissionMiddleware("settings", "read"), handlers.AdminGetAttachments)
|
||||
authAdmin.PUT("/attachments/:id", middleware.PermissionMiddleware("settings", "update"), handlers.AdminUpdateAttachment)
|
||||
authAdmin.DELETE("/attachments/:id", middleware.PermissionMiddleware("settings", "delete"), handlers.AdminDeleteAttachment)
|
||||
|
||||
// 附件分类管理
|
||||
|
||||
176
server/migrations/add_default_oss_configs.sql
Normal file
@@ -0,0 +1,176 @@
|
||||
-- 修改oss_configs表结构,将access_key和secret_key改为VARCHAR(255)并设置默认值
|
||||
ALTER TABLE `oss_configs`
|
||||
MODIFY COLUMN `access_key` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
MODIFY COLUMN `secret_key` VARCHAR(255) NOT NULL DEFAULT '';
|
||||
|
||||
-- 添加阿里云OSS专用字段(使用存储过程安全添加,如果已存在则跳过)
|
||||
SET @dbname = DATABASE();
|
||||
SET @tablename = 'oss_configs';
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'oss_access_key_id') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `oss_access_key_id` VARCHAR(255) NOT NULL DEFAULT '' AFTER `secret_key`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'oss_access_key_secret') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `oss_access_key_secret` VARCHAR(255) NOT NULL DEFAULT '' AFTER `oss_access_key_id`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'oss_endpoint') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `oss_endpoint` VARCHAR(255) NOT NULL DEFAULT '' AFTER `oss_access_key_secret`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'oss_bucket') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `oss_bucket` VARCHAR(255) NOT NULL DEFAULT '' AFTER `oss_endpoint`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'oss_domain') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `oss_domain` VARCHAR(255) NOT NULL DEFAULT '' AFTER `oss_bucket`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 添加腾讯云COS专用字段
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'qcloud_secret_id') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `qcloud_secret_id` VARCHAR(255) NOT NULL DEFAULT '' AFTER `oss_domain`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'qcloud_secret_key') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `qcloud_secret_key` VARCHAR(255) NOT NULL DEFAULT '' AFTER `qcloud_secret_id`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'qcloud_region') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `qcloud_region` VARCHAR(255) NOT NULL DEFAULT '' AFTER `qcloud_secret_key`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'qcloud_bucket') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `qcloud_bucket` VARCHAR(255) NOT NULL DEFAULT '' AFTER `qcloud_region`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'qcloud_domain') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `qcloud_domain` VARCHAR(255) NOT NULL DEFAULT '' AFTER `qcloud_bucket`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 添加七牛云专用字段
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'qiniu_access_key') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `qiniu_access_key` VARCHAR(255) NOT NULL DEFAULT '' AFTER `qcloud_domain`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'qiniu_secret_key') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `qiniu_secret_key` VARCHAR(255) NOT NULL DEFAULT '' AFTER `qiniu_access_key`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'qiniu_bucket') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `qiniu_bucket` VARCHAR(255) NOT NULL DEFAULT '' AFTER `qiniu_secret_key`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'qiniu_region') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `qiniu_region` VARCHAR(255) NOT NULL DEFAULT '' AFTER `qiniu_bucket`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
SET @preparedStatement = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = 'qiniu_domain') > 0,
|
||||
'SELECT 1',
|
||||
'ALTER TABLE `oss_configs` ADD COLUMN `qiniu_domain` VARCHAR(255) NOT NULL DEFAULT '' AFTER `qiniu_region`'
|
||||
));
|
||||
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||
EXECUTE alterIfNotExists;
|
||||
DEALLOCATE PREPARE alterIfNotExists;
|
||||
|
||||
-- 插入默认OSS配置(如果不存在)
|
||||
INSERT INTO `oss_configs` (`name`, `storage_type`, `access_key`, `secret_key`, `bucket`, `region`, `domain`, `is_active`, `deleted_at`, `created_at`, `updated_at`)
|
||||
SELECT '本地存储', 'local', '', '', '', '', '', 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `oss_configs` WHERE `name` = '本地存储' AND `storage_type` = 'local' AND `deleted_at` = 0);
|
||||
|
||||
INSERT INTO `oss_configs` (`name`, `storage_type`, `access_key`, `secret_key`, `bucket`, `region`, `domain`, `is_active`, `deleted_at`, `created_at`, `updated_at`)
|
||||
SELECT '阿里云OSS', 'aliyun', '', '', '', '', '', 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `oss_configs` WHERE `name` = '阿里云OSS' AND `storage_type` = 'aliyun' AND `deleted_at` = 0);
|
||||
|
||||
INSERT INTO `oss_configs` (`name`, `storage_type`, `access_key`, `secret_key`, `bucket`, `region`, `domain`, `is_active`, `deleted_at`, `created_at`, `updated_at`)
|
||||
SELECT '腾讯云COS', 'qcloud', '', '', '', '', '', 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `oss_configs` WHERE `name` = '腾讯云COS' AND `storage_type` = 'qcloud' AND `deleted_at` = 0);
|
||||
|
||||
INSERT INTO `oss_configs` (`name`, `storage_type`, `access_key`, `secret_key`, `bucket`, `region`, `domain`, `is_active`, `deleted_at`, `created_at`, `updated_at`)
|
||||
SELECT '七牛云', 'qiniu', '', '', '', '', '', 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `oss_configs` WHERE `name` = '七牛云' AND `storage_type` = 'qiniu' AND `deleted_at` = 0);
|
||||
3
server/migrations/add_work_links_field.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- 为works表添加links字段(如果不存在)
|
||||
ALTER TABLE `works`
|
||||
ADD COLUMN IF NOT EXISTS `links` TEXT NULL COMMENT '作品链接(JSON格式)' AFTER `description`;
|
||||
@@ -92,16 +92,38 @@ func (a *Attachment) BeforeUpdate(tx *gorm.DB) error {
|
||||
type OSSConfig struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
StorageType string `json:"storageType" gorm:"column:storage_type"` // local/qcloud/aliyun/qiniu
|
||||
AccessKey string `json:"accessKey" gorm:"column:access_key;type:text"` // AES加密存储
|
||||
SecretKey string `json:"secretKey" gorm:"column:secret_key;type:text"` // AES加密存储
|
||||
Bucket string `json:"bucket" gorm:"column:bucket"`
|
||||
Region string `json:"region" gorm:"column:region"`
|
||||
Domain string `json:"domain" gorm:"column:domain"`
|
||||
IsActive int `json:"isActive" gorm:"column:is_active;default:0"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
StorageType string `json:"storageType" gorm:"column:storage_type"` // local/qcloud/aliyun/qiniu
|
||||
AccessKey string `json:"accessKey" gorm:"column:access_key;type:varchar(255);default:'';not null"` // AES加密存储,默认空字符串(向后兼容)
|
||||
SecretKey string `json:"secretKey" gorm:"column:secret_key;type:varchar(255);default:'';not null"` // AES加密存储,默认空字符串(向后兼容)
|
||||
Bucket string `json:"bucket" gorm:"column:bucket"` // 向后兼容
|
||||
Region string `json:"region" gorm:"column:region"` // 向后兼容
|
||||
Domain string `json:"domain" gorm:"column:domain"` // 向后兼容
|
||||
|
||||
// 阿里云OSS专用字段
|
||||
OSSAccessKeyID string `json:"ossAccessKeyId" gorm:"column:oss_access_key_id;type:varchar(255);default:'';not null"`
|
||||
OSSAccessKeySecret string `json:"ossAccessKeySecret" gorm:"column:oss_access_key_secret;type:varchar(255);default:'';not null"`
|
||||
OSSEndpoint string `json:"ossEndpoint" gorm:"column:oss_endpoint;type:varchar(255);default:'';not null"`
|
||||
OSSBucket string `json:"ossBucket" gorm:"column:oss_bucket;type:varchar(255);default:'';not null"`
|
||||
OSSDomain string `json:"ossDomain" gorm:"column:oss_domain;type:varchar(255);default:'';not null"`
|
||||
|
||||
// 腾讯云COS专用字段
|
||||
QCloudSecretID string `json:"qcloudSecretId" gorm:"column:qcloud_secret_id;type:varchar(255);default:'';not null"`
|
||||
QCloudSecretKey string `json:"qcloudSecretKey" gorm:"column:qcloud_secret_key;type:varchar(255);default:'';not null"`
|
||||
QCloudRegion string `json:"qcloudRegion" gorm:"column:qcloud_region;type:varchar(255);default:'';not null"`
|
||||
QCloudBucket string `json:"qcloudBucket" gorm:"column:qcloud_bucket;type:varchar(255);default:'';not null"`
|
||||
QCloudDomain string `json:"qcloudDomain" gorm:"column:qcloud_domain;type:varchar(255);default:'';not null"`
|
||||
|
||||
// 七牛云专用字段
|
||||
QiniuAccessKey string `json:"qiniuAccessKey" gorm:"column:qiniu_access_key;type:varchar(255);default:'';not null"`
|
||||
QiniuSecretKey string `json:"qiniuSecretKey" gorm:"column:qiniu_secret_key;type:varchar(255);default:'';not null"`
|
||||
QiniuBucket string `json:"qiniuBucket" gorm:"column:qiniu_bucket;type:varchar(255);default:'';not null"`
|
||||
QiniuRegion string `json:"qiniuRegion" gorm:"column:qiniu_region;type:varchar(255);default:'';not null"`
|
||||
QiniuDomain string `json:"qiniuDomain" gorm:"column:qiniu_domain;type:varchar(255);default:'';not null"`
|
||||
|
||||
IsActive int `json:"isActive" gorm:"column:is_active;default:0"`
|
||||
DeletedAt int64 `json:"deletedAt" gorm:"column:deleted_at;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
|
||||
@@ -14,6 +14,7 @@ type Work struct {
|
||||
Year string `json:"year" gorm:"column:year"`
|
||||
HeroImg string `json:"heroImg" gorm:"column:hero_img"`
|
||||
Description string `json:"desc" gorm:"column:description;type:text"`
|
||||
Links string `json:"links" gorm:"column:links;type:text"` // JSON格式存储链接
|
||||
IsFeatured int `json:"isFeatured" gorm:"column:is_featured;default:0"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
Target Server Version : 80407 (8.4.7)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/01/2026 13:58:42
|
||||
Date: 19/01/2026 20:20:52
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
@@ -41,7 +41,7 @@ CREATE TABLE `about_profiles` (
|
||||
-- ----------------------------
|
||||
-- Records of about_profiles
|
||||
-- ----------------------------
|
||||
INSERT IGNORE INTO `about_profiles` (`id`, `name`, `avatar`, `location`, `bio`, `email`, `wechat`, `tech_stack`, `experiences`, `is_primary`, `created_at`, `updated_at`, `deleted_at`) VALUES (3, '年糕崽崽', 'https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4', '中国 · 浙江杭州', '嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。从早期的 jQuery 插件开发到如今的 WebGL 3D 场景构建,我始终认为:<strong>技术是骨架,艺术是灵魂。</strong><br><br>目前,我专注于高性能 B 端应用的体验升级,以及探索生成式艺术(Generative Art)在网页中的应用。闲暇之余,我喜欢在杭州的西湖边散步,或者捣腾我的客制化机械键盘。', 'liqiworker@gmail.com', 'ngzz_0218', '[\\\"Vue 3\\\",\\\"React\\\",\\\"TypeScript\\\",\\\"Three.js\\\",\\\"Golang\\\",\\\"Tailwind CSS\\\",\\\"Rust\\\",\\\"Wails\\\"]', '[{\\\"year\\\":\\\"2024 - 至今\\\",\\\"role\\\":\\\"技术负责人\\\",\\\"company\\\":\\\"某医疗平台公司\\\"},{\\\"year\\\":\\\"2020 - 2024\\\",\\\"role\\\":\\\"PHP开发工程师\\\",\\\"company\\\":\\\"某电商公司\\\"}]', 0, 0, 0, 0);
|
||||
INSERT INTO `about_profiles` VALUES (3, '年糕崽崽', 'https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4', '中国 · 浙江杭州', '嗨!我是年糕崽崽,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。从早期的 jQuery 插件开发到如今的 WebGL 3D 场景构建,我始终认为:<strong>技术是骨架,艺术是灵魂。</strong><br><br>目前,我专注于高性能 B 端应用的体验升级,以及探索生成式艺术(Generative Art)在网页中的应用。闲暇之余,我喜欢在杭州的西湖边散步,或者捣腾我的客制化机械键盘。', 'liqiworker@gmail.com', 'ngzz_0218', '[\\\"Vue 3\\\",\\\"React\\\",\\\"TypeScript\\\",\\\"Three.js\\\",\\\"Golang\\\",\\\"Tailwind CSS\\\",\\\"Rust\\\",\\\"Wails\\\"]', '[{\\\"year\\\":\\\"2024 - 至今\\\",\\\"role\\\":\\\"技术负责人\\\",\\\"company\\\":\\\"某医疗平台公司\\\"},{\\\"year\\\":\\\"2020 - 2024\\\",\\\"role\\\":\\\"PHP开发工程师\\\",\\\"company\\\":\\\"某电商公司\\\"}]', 0, 0, 0, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for access_logs
|
||||
@@ -66,6 +66,66 @@ CREATE TABLE `access_logs` (
|
||||
-- Records of access_logs
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for attachment_categories
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `attachment_categories`;
|
||||
CREATE TABLE `attachment_categories` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '分类描述',
|
||||
`sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件分类表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of attachment_categories
|
||||
-- ----------------------------
|
||||
INSERT INTO `attachment_categories` VALUES (1, '文章附件', '在博客中上传的附件', 0, 0, 1768812104, 1768812104);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for attachments
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `attachments`;
|
||||
CREATE TABLE `attachments` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`category_id` int UNSIGNED NULL DEFAULT NULL COMMENT '附件分类ID',
|
||||
`original_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始文件名',
|
||||
`stored_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '存储文件名',
|
||||
`file_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件路径',
|
||||
`file_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件访问URL',
|
||||
`file_size` bigint UNSIGNED NOT NULL DEFAULT 0 COMMENT '文件大小(字节)',
|
||||
`file_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件类型: image/video/document/other',
|
||||
`mime_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'MIME类型',
|
||||
`storage_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'local' COMMENT '存储类型: local/qcloud/aliyun/qiniu',
|
||||
`oss_config_id` int UNSIGNED NULL DEFAULT NULL COMMENT 'OSS配置ID',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_category_id`(`category_id` ASC) USING BTREE,
|
||||
INDEX `idx_storage_type`(`storage_type` ASC) USING BTREE,
|
||||
INDEX `idx_file_type`(`file_type` ASC) USING BTREE,
|
||||
INDEX `idx_created_at`(`created_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of attachments
|
||||
-- ----------------------------
|
||||
INSERT INTO `attachments` VALUES (1, NULL, 'cc_upload_xTUFrRBdm7zWyjQs693a39ea93969.jpg', 'cc_upload_xTUFrRBdm7zWyjQs693a39ea93969.jpg', 'uploads\\2026\\01\\19\\cc_upload_xTUFrRBdm7zWyjQs693a39ea93969_1768807852.jpg', '/uploads/2026/01/19/cc_upload_xTUFrRBdm7zWyjQs693a39ea93969_1768807852.jpg', 40648, 'image', 'image/jpeg', 'local', NULL, 0, 1768807852, 1768807852);
|
||||
INSERT INTO `attachments` VALUES (2, 1, 'cc_upload_opYFPBlXkfyOSrH269152a21424c4.jpg', 'cc_upload_opYFPBlXkfyOSrH269152a21424c4.jpg', 'uploads\\2026\\01\\19\\cc_upload_opYFPBlXkfyOSrH269152a21424c4_1768811098.jpg', '/uploads/2026/01/19/cc_upload_opYFPBlXkfyOSrH269152a21424c4_1768811098.jpg', 12911, 'image', 'image/jpeg', 'local', NULL, 0, 1768811098, 1768811098);
|
||||
INSERT INTO `attachments` VALUES (3, 1, 'cc_upload_xTUFrRBdm7zWyjQs693a39ea93969.jpg', 'cc_upload_xTUFrRBdm7zWyjQs693a39ea93969.jpg', 'uploads\\2026\\01\\19\\cc_upload_xTUFrRBdm7zWyjQs693a39ea93969_1768824015.jpg', '/uploads/2026/01/19/cc_upload_xTUFrRBdm7zWyjQs693a39ea93969_1768824015.jpg', 40648, 'image', 'image/jpeg', 'local', NULL, 0, 1768824015, 1768824015);
|
||||
INSERT INTO `attachments` VALUES (4, 1, 'cc_upload_0NeLXeWx8AmBi7Mx693ce05fea320.jpg', 'cc_upload_0NeLXeWx8AmBi7Mx693ce05fea320.jpg', 'uploads\\2026\\01\\19\\cc_upload_0NeLXeWx8AmBi7Mx693ce05fea320_1768824028.jpg', '/uploads/2026/01/19/cc_upload_0NeLXeWx8AmBi7Mx693ce05fea320_1768824028.jpg', 19875, 'image', 'image/jpeg', 'local', NULL, 0, 1768824029, 1768824029);
|
||||
INSERT INTO `attachments` VALUES (5, 1, 'cc_upload_62sXZ0HMvINRUYDO693d6c27c7045.jpg', 'cc_upload_62sXZ0HMvINRUYDO693d6c27c7045.jpg', 'uploads\\2026\\01\\19\\cc_upload_62sXZ0HMvINRUYDO693d6c27c7045_1768824028.jpg', '/uploads/2026/01/19/cc_upload_62sXZ0HMvINRUYDO693d6c27c7045_1768824028.jpg', 305101, 'image', 'image/jpeg', 'local', NULL, 0, 1768824029, 1768824029);
|
||||
INSERT INTO `attachments` VALUES (6, 1, 'cc_upload_9AqkM4FiiRhzl1On693d6c27ce6eb.jpg', 'cc_upload_9AqkM4FiiRhzl1On693d6c27ce6eb.jpg', 'uploads\\2026\\01\\19\\cc_upload_9AqkM4FiiRhzl1On693d6c27ce6eb_1768824028.jpg', '/uploads/2026/01/19/cc_upload_9AqkM4FiiRhzl1On693d6c27ce6eb_1768824028.jpg', 237431, 'image', 'image/jpeg', 'local', NULL, 0, 1768824029, 1768824029);
|
||||
INSERT INTO `attachments` VALUES (7, 1, 'cc_upload_gXTnJhQ2DSbBr3Ql693d6c27c0b62.jpg', 'cc_upload_gXTnJhQ2DSbBr3Ql693d6c27c0b62.jpg', 'uploads\\2026\\01\\19\\cc_upload_gXTnJhQ2DSbBr3Ql693d6c27c0b62_1768824028.jpg', '/uploads/2026/01/19/cc_upload_gXTnJhQ2DSbBr3Ql693d6c27c0b62_1768824028.jpg', 210572, 'image', 'image/jpeg', 'local', NULL, 0, 1768824029, 1768824029);
|
||||
INSERT INTO `attachments` VALUES (8, 1, 'cc_upload_etVM7GrPwl33KYzn693cc3016abdf.png', 'cc_upload_etVM7GrPwl33KYzn693cc3016abdf.png', 'uploads\\2026\\01\\19\\cc_upload_etVM7GrPwl33KYzn693cc3016abdf_1768824028.png', '/uploads/2026/01/19/cc_upload_etVM7GrPwl33KYzn693cc3016abdf_1768824028.png', 1161285, 'image', 'image/png', 'local', NULL, 0, 1768824029, 1768824029);
|
||||
INSERT INTO `attachments` VALUES (9, 1, 'cc_upload_HiPDFg6oL0Q5ijfo693ce1f5eb57c.jpg', 'cc_upload_HiPDFg6oL0Q5ijfo693ce1f5eb57c.jpg', 'uploads\\2026\\01\\19\\cc_upload_HiPDFg6oL0Q5ijfo693ce1f5eb57c_1768825204.jpg', '/uploads/2026/01/19/cc_upload_HiPDFg6oL0Q5ijfo693ce1f5eb57c_1768825204.jpg', 1674408, 'image', 'image/jpeg', 'local', NULL, 0, 1768825204, 1768825204);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for categories
|
||||
-- ----------------------------
|
||||
@@ -108,12 +168,11 @@ CREATE TABLE `column_posts` (
|
||||
-- ----------------------------
|
||||
-- Records of column_posts
|
||||
-- ----------------------------
|
||||
INSERT INTO `column_posts` VALUES (1, 1, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (2, 2, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (3, 3, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (4, 4, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (4, 5, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (4, 6, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (0, 2, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (0, 3, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (1, 4, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (1, 5, 0, 0);
|
||||
INSERT INTO `column_posts` VALUES (1, 6, 0, 1768809352);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for columns
|
||||
@@ -191,10 +250,7 @@ CREATE TABLE `inquiries` (
|
||||
-- ----------------------------
|
||||
-- Table structure for operation_logs
|
||||
-- ----------------------------
|
||||
-- Ensure table is dropped even if it has dependencies
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
DROP TABLE IF EXISTS `operation_logs`;
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
CREATE TABLE `operation_logs` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` bigint UNSIGNED NOT NULL COMMENT '操作用户ID',
|
||||
@@ -211,6 +267,40 @@ CREATE TABLE `operation_logs` (
|
||||
INDEX `idx_user_id`(`user_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 644 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of operation_logs
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for oss_configs
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `oss_configs`;
|
||||
CREATE TABLE `oss_configs` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '配置名称',
|
||||
`storage_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '存储类型: local/qcloud/aliyun/qiniu',
|
||||
`access_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`secret_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`bucket` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '存储桶名称',
|
||||
`region` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '区域',
|
||||
`domain` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '访问域名',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否启用',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_storage_type`(`storage_type` ASC) USING BTREE,
|
||||
INDEX `idx_is_active`(`is_active` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OSS配置表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of oss_configs
|
||||
-- ----------------------------
|
||||
INSERT INTO `oss_configs` VALUES (1, '本地存储', 'local', '', '', '', '', '', 1, 0, 1768820300, 1768820300);
|
||||
INSERT INTO `oss_configs` VALUES (2, '阿里云OSS', 'aliyun', '', '', '', '', '', 0, 0, 1768820300, 1768820300);
|
||||
INSERT INTO `oss_configs` VALUES (3, '腾讯云COS', 'qcloud', '', '', '', '', '', 0, 0, 1768820300, 1768820300);
|
||||
INSERT INTO `oss_configs` VALUES (4, '七牛云', 'qiniu', '', '', '', '', '', 0, 0, 1768820300, 1768820300);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for partners
|
||||
-- ----------------------------
|
||||
@@ -307,11 +397,14 @@ CREATE TABLE `post_history` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_post_id`(`post_id` ASC) USING BTREE,
|
||||
INDEX `idx_version`(`version` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章历史记录表' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文章历史记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of post_history
|
||||
-- ----------------------------
|
||||
INSERT INTO `post_history` VALUES (1, 6, 1, 'Goravel 入门指南 (三):ORM 数据库操作', 4, '掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。', '', 1, 1, 1768809133, 1768809133, 0);
|
||||
INSERT INTO `post_history` VALUES (2, 6, 2, 'Goravel 入门指南 (三):ORM 数据库操作', 4, '掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。', '', 1, 1, 1768809352, 1768809352, 0);
|
||||
INSERT INTO `post_history` VALUES (3, 1, 1, '重构的艺术:如何优雅地处理遗留代码', 4, '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '<h2>什么是代码重构?</h2><p>代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...</p>\n\n\n\n', 1, 1, 1768811113, 1768811113, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for post_tags
|
||||
@@ -328,6 +421,10 @@ CREATE TABLE `post_tags` (
|
||||
-- ----------------------------
|
||||
-- Records of post_tags
|
||||
-- ----------------------------
|
||||
INSERT INTO `post_tags` VALUES (1, 6, 0);
|
||||
INSERT INTO `post_tags` VALUES (2, 6, 0);
|
||||
INSERT INTO `post_tags` VALUES (3, 6, 0);
|
||||
INSERT INTO `post_tags` VALUES (4, 6, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for posts
|
||||
@@ -357,12 +454,12 @@ CREATE TABLE `posts` (
|
||||
-- ----------------------------
|
||||
-- Records of posts
|
||||
-- ----------------------------
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (1, 'refactor', '重构的艺术:如何优雅地处理遗留代码', 4, NULL, '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '<h2>什么是代码重构?</h2><p>代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...</p>', 5, 1, 0, 1768291814, 1768538949);
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (2, 'shader', '着色器魔法:从零开始写一个噪声生成器', 2, NULL, '深入了解WebGL着色器,学习如何从零开始实现一个高性能的噪声生成器,为你的3D作品增添独特的视觉效果。', ' <p>GLSL (OpenGL Shading Language) 是一门让人生畏但也充满魅力的语言。它运行在 GPU 上,能够并行处理数百万个像素,创造出惊人的视觉效果。</p>\r\n <h2>什么是柏林噪声?</h2>\r\n <p>柏林噪声(Perlin Noise)是一种梯度噪声,它比普通的随机数生成的噪声看起来更自然、更平滑。它常被用来模拟云彩、地形、火焰等自然现象。</p>\r\n <h2>Three.js 中的实现</h2>\r\n <p>在 Three.js 中,我们可以通过 <code>ShaderMaterial</code> 直接编写 GLSL 代码。</p>\r\n <pre><code><span class=\"code-comment\">// 简单的顶点着色器</span>\r\n<span class=\"code-keyword\">varying</span> <span class=\"code-keyword\">vec2</span> vUv;\r\n<span class=\"code-keyword\">void</span> <span class=\"code-func\">main</span>() {\r\n vUv = uv;\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r\n}</code></pre>\r\n <p>通过调整噪声的频率和振幅,我们可以得到各种不同的纹理效果。在我的个人网站背景中,就使用了这种技术来生成流动的极光效果。</p>\r\n ', 1, 1, 0, 1768291815, 1768538949);
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (3, 'ux', '用户体验设计:从认知心理学到交互实践', 3, NULL, '探索用户体验设计的核心原理,结合认知心理学知识,学习如何设计出真正符合用户需求的交互界面。', '<h2>认知心理学在UX设计中的应用</h2><p>了解用户的认知过程是设计良好用户体验的基础...</p>', 0, 1, 0, 1768291816, 1768538949);
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (4, 'goravel-guide-1', 'Goravel 入门指南 (一):环境搭建与 Hello World', 4, 1, '本文将带你了解 Go 语言领域的 Laravel —— Goravel 框架,并演示如何快速搭建环境及运行第一个 Web 服务。', '## 什么是 Goravel?\n\nGoravel 是一个基于 Go 语言的 Web 开发框架,它的设计理念深受 PHP Laravel 框架的启发。如果你是一名从 PHP 转 Go 的开发者,或者你喜欢 Laravel 那种“开箱即用、优雅简洁”的开发体验,那么 Goravel 绝对是你的不二之选。\n\n它集成了丰富的功能模块,包括但不限于:\n- 强大的路由系统\n- ORM(基于 GORM 封装)\n- 依赖注入容器\n- 队列与任务调度\n- 缓存与文件存储\n\n## 环境搭建\n\nGoravel 提供了一个名为 `knit` 的命令行工具(类似 Laravel 的 artisan),可以帮助我们快速初始化项目。\n\n### 1. 安装 Knit CLI\n\n确保你已经安装了 Go (1.20+),然后运行以下命令:\n\n```bash\ngo install github.com/goravel/knit/cmd/knit@latest\n```\n\n### 2. 创建新项目\n\n使用 `knit new` 命令创建项目:\n\n```bash\nknit new my-goravel-app\ncd my-goravel-app\n```\n\n### 3. 安装依赖\n\n```bash\ngo mod tidy\n```\n\n## 目录结构\n\n打开项目,你会发现它的目录结构非常清晰,带有浓厚的 Laravel 风格:\n\n- **app/**: 核心业务代码(Http 控制器、模型、服务提供者等)\n- **config/**: 配置文件(应用配置、数据库配置等)\n- **routes/**: 路由定义文件\n- **database/**: 数据库迁移与填充\n- **public/**: 静态资源文件\n\n## 运行 Hello World\n\nGoravel 的入口文件是根目录下的 `main.go`。在运行之前,我们先看一眼路由定义。打开 `routes/web.go`:\n\n```go\npackage routes\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n \"github.com/goravel/framework/facades\"\n)\n\nfunc Web() {\n facades.Route().Get(\"/\", func(ctx http.Context) http.Response {\n return ctx.Response().Json(200, http.Json{\n \"Hello\": \"Goravel\",\n })\n })\n}\n```\n\n非常直观!现在让我们启动服务:\n\n```bash\ngo run .\n```\n\n默认情况下,服务会运行在 `http://localhost:3000`。打开浏览器访问,你应该能看到 JSON 响应:\n\n```json\n{\n \"Hello\": \"Goravel\"\n}\n```\n\n至此,你已经成功运行了你的第一个 Goravel 应用!在下一篇文章中,我们将深入探讨路由与控制器的使用。', 1210, 1, 0, 1768465932, 1768538949);
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (5, 'goravel-guide-2', 'Goravel 入门指南 (二):路由与控制器', 4, 1, '深入理解 Goravel 的 HTTP 层,学习如何定义 RESTful 路由、创建控制器以及处理 HTTP 请求与响应。', '## 路由系统\n\n在 Goravel 中,路由定义通常位于 `routes/` 目录下。`api.go` 用于定义 API 路由,`web.go` 用于定义网页路由。Goravel 使用 `facades.Route()` 来定义路由,这得益于其强大的依赖注入系统。\n\n### 基础路由\n\n```go\n// GET 请求\nfacades.Route().Get(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().String(200, \"User List\")\n})\n\n// POST 请求\nfacades.Route().Post(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().Success().Json(http.Json{\"id\": 1})\n})\n```\n\n### 路由参数\n\n获取 URL 中的动态参数非常简单:\n\n```go\nfacades.Route().Get(\"/users/{id}\", func(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n return ctx.Response().Success().Json(http.Json{\"user_id\": id})\n})\n```\n\n## 控制器 (Controllers)\n\n随着应用变大,我们不可能把所有逻辑都写在路由闭包里。这时候就需要控制器了。\n\n### 创建控制器\n\n使用 `knit` 工具可以快速生成控制器:\n\n```bash\nknit make:controller UserController\n```\n\n这会在 `app/http/controllers` 目录下生成 `user_controller.go`。让我们修改它来添加一个 `Show` 方法:\n\n```go\npackage controllers\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n)\n\ntype UserController struct {\n // 可以在这里注入服务\n}\n\nfunc NewUserController() *UserController {\n return &UserController{}\n}\n\nfunc (r *UserController) Show(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n // 模拟数据库查询\n return ctx.Response().Success().Json(http.Json{\n \"id\": id,\n \"name\": \"Goravel User\",\n })\n}\n```\n\n### 注册控制器路由\n\n回到 `routes/api.go`,我们需要先实例化控制器,然后绑定路由:\n\n```go\nimport \"my-goravel-app/app/http/controllers\"\n\nfunc Api() {\n userController := controllers.NewUserController()\n \n // 绑定到控制器方法\n facades.Route().Get(\"/users/{id}\", userController.Show)\n}\n```\n\n## 请求与响应\n\n在控制器方法中,`ctx` (http.Context) 是核心:\n\n- **获取输入**: `ctx.Request().Input(\"key\")`\n- **获取 JSON**: `ctx.Request().Bind(&user)`\n- **返回 JSON**: `ctx.Response().Json(200, data)`\n- **设置状态码**: `ctx.Response().Status(404)`\n\n通过这种方式,Goravel 让 HTTP 层的处理变得异常清晰和标准化。下一章,我们将学习如何通过 ORM 操作数据库。', 901, 1, 0, 1768465933, 1768538949);
|
||||
INSERT IGNORE INTO `posts` (`id`, `original_id`, `title`, `category_id`, `column_id`, `excerpt`, `content`, `read_count`, `is_published`, `deleted_at`, `created_at`, `updated_at`) VALUES (6, 'goravel-guide-3', 'Goravel 入门指南 (三):ORM 数据库操作', 4, 1, '掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。', '## Goravel ORM 简介\n\nGoravel 的 ORM 基于著名的 GORM 库构建,但它进行了一层优雅的封装,使其调用方式更接近 Laravel 的 Eloquent ORM。这意味着你可以使用 Facade 模式轻松调用数据库,且支持自动迁移。\n\n## 配置数据库\n\n打开 `config/database.go` 或 `.env` 文件,配置你的 MySQL 连接信息:\n\n```env\nDB_CONNECTION=mysql\nDB_HOST=127.0.0.1\nDB_PORT=3306\nDB_DATABASE=goravel\nDB_USERNAME=root\nDB_PASSWORD=password\n```\n\n## 定义模型\n\n使用 `knit` 生成模型:\n\n```bash\nknit make:model Post\n```\n\n生成的模型文件位于 `app/models/post.go`。我们需要定义结构体字段:\n\n```go\npackage models\n\nimport (\n \"github.com/goravel/framework/database/orm\"\n)\n\ntype Post struct {\n orm.Model\n Title string `gorm:\"size:255;not null\"`\n Content string `gorm:\"type:text\"`\n UserID uint\n}\n```\n\n## 数据库迁移\n\n虽然 GORM 支持 AutoMigrate,但 Goravel 推荐使用迁移文件来管理数据库变更。\n\n```bash\nknit make:migration create_posts_table\n```\n\n编辑生成的迁移文件 (`database/migrations/xxxx_create_posts_table.go`),定义表结构,然后运行:\n\n```bash\nknit migrate\n```\n\n## CRUD 操作\n\n有了模型,我们就可以愉快地操作数据库了。所有操作都通过 `facades.Orm()` 入口。\n\n### 创建 (Create)\n\n```go\npost := models.Post{\n Title: \"My First Post\",\n Content: \"Content goes here...\",\n}\nerr := facades.Orm().Query().Create(&post)\n```\n\n### 查询 (Read)\n\n```go\nvar post models.Post\n// 根据主键查询\nfacades.Orm().Query().Find(&post, 1)\n\n// 条件查询\nvar posts []models.Post\nfacades.Orm().Query().Where(\"title\", \"My First Post\").Get(&posts)\n```\n\n### 更新 (Update)\n\n```go\nvar post models.Post\nfacades.Orm().Query().Find(&post, 1)\n\npost.Title = \"Updated Title\"\nfacades.Orm().Query().Save(&post)\n```\n\n### 删除 (Delete)\n\n```go\nvar post models.Post\nfacades.Orm().Query().Delete(&post, 1)\n```\n\nGoravel 的 ORM 让 Go 语言的数据库操作不再繁琐,极大地提高了开发效率。配合之前的路由和控制器知识,你现在已经具备了开发完整 RESTful API 的能力!', 1569, 1, 0, 1768465934, 1768538949);
|
||||
INSERT INTO `posts` VALUES (1, 'refactor', '重构的艺术:如何优雅地处理遗留代码', 4, NULL, '在本文中,我们将探讨重构的核心原则和实用技巧,帮助你优雅地处理遗留代码,提高代码质量和可维护性。', '<h2>什么是代码重构?</h2><p>代码重构是在不改变代码外部行为的前提下,优化代码内部结构的过程...</p>\n\n\n\n', 9, 1, 0, 1768291814, 1768811113);
|
||||
INSERT INTO `posts` VALUES (2, 'shader', '着色器魔法:从零开始写一个噪声生成器', 2, NULL, '深入了解WebGL着色器,学习如何从零开始实现一个高性能的噪声生成器,为你的3D作品增添独特的视觉效果。', ' <p>GLSL (OpenGL Shading Language) 是一门让人生畏但也充满魅力的语言。它运行在 GPU 上,能够并行处理数百万个像素,创造出惊人的视觉效果。</p>\r\n <h2>什么是柏林噪声?</h2>\r\n <p>柏林噪声(Perlin Noise)是一种梯度噪声,它比普通的随机数生成的噪声看起来更自然、更平滑。它常被用来模拟云彩、地形、火焰等自然现象。</p>\r\n <h2>Three.js 中的实现</h2>\r\n <p>在 Three.js 中,我们可以通过 <code>ShaderMaterial</code> 直接编写 GLSL 代码。</p>\r\n <pre><code><span class=\"code-comment\">// 简单的顶点着色器</span>\r\n<span class=\"code-keyword\">varying</span> <span class=\"code-keyword\">vec2</span> vUv;\r\n<span class=\"code-keyword\">void</span> <span class=\"code-func\">main</span>() {\r\n vUv = uv;\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r\n}</code></pre>\r\n <p>通过调整噪声的频率和振幅,我们可以得到各种不同的纹理效果。在我的个人网站背景中,就使用了这种技术来生成流动的极光效果。</p>\r\n ', 1, 1, 0, 1768291815, 1768538949);
|
||||
INSERT INTO `posts` VALUES (3, 'ux', '用户体验设计:从认知心理学到交互实践', 3, NULL, '探索用户体验设计的核心原理,结合认知心理学知识,学习如何设计出真正符合用户需求的交互界面。', '<h2>认知心理学在UX设计中的应用</h2><p>了解用户的认知过程是设计良好用户体验的基础...</p>', 0, 1, 0, 1768291816, 1768538949);
|
||||
INSERT INTO `posts` VALUES (4, 'goravel-guide-1', 'Goravel 入门指南 (一):环境搭建与 Hello World', 4, 1, '本文将带你了解 Go 语言领域的 Laravel —— Goravel 框架,并演示如何快速搭建环境及运行第一个 Web 服务。', '## 什么是 Goravel?\n\nGoravel 是一个基于 Go 语言的 Web 开发框架,它的设计理念深受 PHP Laravel 框架的启发。如果你是一名从 PHP 转 Go 的开发者,或者你喜欢 Laravel 那种“开箱即用、优雅简洁”的开发体验,那么 Goravel 绝对是你的不二之选。\n\n它集成了丰富的功能模块,包括但不限于:\n- 强大的路由系统\n- ORM(基于 GORM 封装)\n- 依赖注入容器\n- 队列与任务调度\n- 缓存与文件存储\n\n## 环境搭建\n\nGoravel 提供了一个名为 `knit` 的命令行工具(类似 Laravel 的 artisan),可以帮助我们快速初始化项目。\n\n### 1. 安装 Knit CLI\n\n确保你已经安装了 Go (1.20+),然后运行以下命令:\n\n```bash\ngo install github.com/goravel/knit/cmd/knit@latest\n```\n\n### 2. 创建新项目\n\n使用 `knit new` 命令创建项目:\n\n```bash\nknit new my-goravel-app\ncd my-goravel-app\n```\n\n### 3. 安装依赖\n\n```bash\ngo mod tidy\n```\n\n## 目录结构\n\n打开项目,你会发现它的目录结构非常清晰,带有浓厚的 Laravel 风格:\n\n- **app/**: 核心业务代码(Http 控制器、模型、服务提供者等)\n- **config/**: 配置文件(应用配置、数据库配置等)\n- **routes/**: 路由定义文件\n- **database/**: 数据库迁移与填充\n- **public/**: 静态资源文件\n\n## 运行 Hello World\n\nGoravel 的入口文件是根目录下的 `main.go`。在运行之前,我们先看一眼路由定义。打开 `routes/web.go`:\n\n```go\npackage routes\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n \"github.com/goravel/framework/facades\"\n)\n\nfunc Web() {\n facades.Route().Get(\"/\", func(ctx http.Context) http.Response {\n return ctx.Response().Json(200, http.Json{\n \"Hello\": \"Goravel\",\n })\n })\n}\n```\n\n非常直观!现在让我们启动服务:\n\n```bash\ngo run .\n```\n\n默认情况下,服务会运行在 `http://localhost:3000`。打开浏览器访问,你应该能看到 JSON 响应:\n\n```json\n{\n \"Hello\": \"Goravel\"\n}\n```\n\n至此,你已经成功运行了你的第一个 Goravel 应用!在下一篇文章中,我们将深入探讨路由与控制器的使用。', 1210, 1, 0, 1768465932, 1768538949);
|
||||
INSERT INTO `posts` VALUES (5, 'goravel-guide-2', 'Goravel 入门指南 (二):路由与控制器', 4, 1, '深入理解 Goravel 的 HTTP 层,学习如何定义 RESTful 路由、创建控制器以及处理 HTTP 请求与响应。', '## 路由系统\n\n在 Goravel 中,路由定义通常位于 `routes/` 目录下。`api.go` 用于定义 API 路由,`web.go` 用于定义网页路由。Goravel 使用 `facades.Route()` 来定义路由,这得益于其强大的依赖注入系统。\n\n### 基础路由\n\n```go\n// GET 请求\nfacades.Route().Get(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().String(200, \"User List\")\n})\n\n// POST 请求\nfacades.Route().Post(\"/users\", func(ctx http.Context) http.Response {\n return ctx.Response().Success().Json(http.Json{\"id\": 1})\n})\n```\n\n### 路由参数\n\n获取 URL 中的动态参数非常简单:\n\n```go\nfacades.Route().Get(\"/users/{id}\", func(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n return ctx.Response().Success().Json(http.Json{\"user_id\": id})\n})\n```\n\n## 控制器 (Controllers)\n\n随着应用变大,我们不可能把所有逻辑都写在路由闭包里。这时候就需要控制器了。\n\n### 创建控制器\n\n使用 `knit` 工具可以快速生成控制器:\n\n```bash\nknit make:controller UserController\n```\n\n这会在 `app/http/controllers` 目录下生成 `user_controller.go`。让我们修改它来添加一个 `Show` 方法:\n\n```go\npackage controllers\n\nimport (\n \"github.com/goravel/framework/contracts/http\"\n)\n\ntype UserController struct {\n // 可以在这里注入服务\n}\n\nfunc NewUserController() *UserController {\n return &UserController{}\n}\n\nfunc (r *UserController) Show(ctx http.Context) http.Response {\n id := ctx.Request().Input(\"id\")\n // 模拟数据库查询\n return ctx.Response().Success().Json(http.Json{\n \"id\": id,\n \"name\": \"Goravel User\",\n })\n}\n```\n\n### 注册控制器路由\n\n回到 `routes/api.go`,我们需要先实例化控制器,然后绑定路由:\n\n```go\nimport \"my-goravel-app/app/http/controllers\"\n\nfunc Api() {\n userController := controllers.NewUserController()\n \n // 绑定到控制器方法\n facades.Route().Get(\"/users/{id}\", userController.Show)\n}\n```\n\n## 请求与响应\n\n在控制器方法中,`ctx` (http.Context) 是核心:\n\n- **获取输入**: `ctx.Request().Input(\"key\")`\n- **获取 JSON**: `ctx.Request().Bind(&user)`\n- **返回 JSON**: `ctx.Response().Json(200, data)`\n- **设置状态码**: `ctx.Response().Status(404)`\n\n通过这种方式,Goravel 让 HTTP 层的处理变得异常清晰和标准化。下一章,我们将学习如何通过 ORM 操作数据库。', 903, 1, 0, 1768465933, 1768538949);
|
||||
INSERT INTO `posts` VALUES (6, 'goravel-guide-3', 'Goravel 入门指南 (三):ORM 数据库操作', 4, 1, '掌握 Goravel 强大的 ORM 功能,从数据库配置、模型定义到执行增删改查(CRUD)操作。', '## Goravel ORM 简介\r\nGoravel 的 ORM 基于著名的 GORM 库构建,但它进行了一层优雅的封装,使其调用方式更接近 Laravel 的 Eloquent ORM。这意味着你可以使用 Facade 模式轻松调用数据库,且支持自动迁移。\r\n## 配置数据库\r\n打开 `config/database.go` 或 `.env` 文件,配置你的 MySQL 连接信息:\r\n```env\r\nDB_CONNECTION=mysql\r\nDB_HOST=127.0.0.1\r\nDB_PORT=3306\r\nDB_DATABASE=goravel\r\nDB_USERNAME=root\r\nDB_PASSWORD=password\r\n```\r\n## 定义模型\r\n使用 `knit` 生成模型:\r\n```bash\r\nknit make:model Post\r\n```\r\n生成的模型文件位于 `app/models/post.go`。我们需要定义结构体字段:\r\n```go\r\npackage models\r\nimport (\r\n \\\"github.com/goravel/framework/database/orm\\\"\r\n )\r\ntype Post struct {\r\n orm.Model\r\n Title string `gorm:\\\"size:255;not null\\\"`\r\n Content string `gorm:\\\"type:text\\\"`\r\n UserID uint\r\n }\r\n ```\r\n## 数据库迁移\r\n虽然 GORM 支持 AutoMigrate,但 Goravel 推荐使用迁移文件来管理数据库变更。\r\n```bash\r\nknit make:migration create_posts_table\r\n```\r\n编辑生成的迁移文件 (`database/migrations/xxxx_create_posts_table.go`),定义表结构,然后运行:\r\n```bash\r\nknit migrate\r\n```\r\n## CRUD 操作\r\n有了模型,我们就可以愉快地操作数据库了。所有操作都通过 `facades.Orm()` 入口。\r\n### 创建 (Create)\r\n```go\r\npost := models.Post{\r\n Title: \\\"My First Post\\\",\r\n Content: \\\"Content goes here...\\\",\r\n }\r\n err := facades.Orm().Query().Create(&post)\r\n ```\r\n### 查询 (Read)\r\n```go\r\nvar post models.Post\r\n// 根据主键查询\r\nfacades.Orm().Query().Find(&post, 1)\r\n// 条件查询\r\nvar posts []models.Post\r\nfacades.Orm().Query().Where(\\\"title\\\", \\\"My First Post\\\").Get(&posts)\r\n```\r\n### 更新 (Update)\r\n```go\r\nvar post models.Post\r\nfacades.Orm().Query().Find(&post, 1)\r\npost.Title = \\\"Updated Title\\\"\r\nfacades.Orm().Query().Save(&post)\r\n```\r\n### 删除 (Delete)\r\n```go\r\nvar post models.Post\r\nfacades.Orm().Query().Delete(&post, 1)\r\n```\r\nGoravel 的 ORM 让 Go 语言的数据库操作不再繁琐,极大地提高了开发效率。配合之前的路由和控制器知识,你现在已经具备了开发完整 RESTful API 的能力!', 1593, 1, 0, 1768465934, 1768809351);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for role_permissions
|
||||
@@ -444,6 +541,27 @@ INSERT INTO `roles` VALUES (1, 'admin', '系统管理员', 0, 1768452892, 176853
|
||||
INSERT INTO `roles` VALUES (2, 'editor', '内容编辑', 0, 1768452892, 1768538956);
|
||||
INSERT INTO `roles` VALUES (3, 'viewer', '普通访客', 0, 1768452892, 1768538956);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for search_logs
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `search_logs`;
|
||||
CREATE TABLE `search_logs` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`keyword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '搜索关键词',
|
||||
`search_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '搜索类型: category/tag/column/keyword',
|
||||
`user_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户IP地址',
|
||||
`user_location` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户归属地',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_search_type`(`search_type` ASC) USING BTREE,
|
||||
INDEX `idx_created_at`(`created_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '搜索记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of search_logs
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for settings
|
||||
-- ----------------------------
|
||||
@@ -494,13 +612,11 @@ CREATE TABLE `snippets` (
|
||||
-- ----------------------------
|
||||
-- Records of snippets
|
||||
-- ----------------------------
|
||||
INSERT INTO `snippets` VALUES ('1', 'React 鼠标追踪 Hook', 'import { useState, useEffect } from \'react\';\r\n\r\nexport const useMousePosition = () => {\r\n const [pos, setPos] = useState({ x: 0, y: 0 });\r\n useEffect(() => {\r\n const update = (e) => setPos({ x: e.clientX, y: e.clientY });\r\n window.addEventListener(\'mousemove\', update);\r\n return () => window.removeEventListener(\'mousemove\', update);\r\n }, []);\r\n return pos;\r\n};', 'mouse', '这是一个鼠标追踪', 2, 0, 1768350739, 1768538952);
|
||||
INSERT INTO `snippets` VALUES ('1', 'React 鼠标追踪 Hook', 'import { useState, useEffect } from \'react\';\r\n\r\nexport const useMousePosition = () => {\r\n const [pos, setPos] = useState({ x: 0, y: 0 });\r\n useEffect(() => {\r\n const update = (e) => setPos({ x: e.clientX, y: e.clientY });\r\n window.addEventListener(\'mousemove\', update);\r\n return () => window.removeEventListener(\'mousemove\', update);\r\n }, []);\r\n return pos;\r\n};', 'mouse', '这是一个鼠标追踪', 3, 0, 1768350739, 1768538952);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for tags
|
||||
-- ----------------------------
|
||||
-- Ensure table is dropped even if it has dependencies
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
DROP TABLE IF EXISTS `tags`;
|
||||
CREATE TABLE `tags` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
@@ -513,12 +629,15 @@ CREATE TABLE `tags` (
|
||||
UNIQUE INDEX `name`(`name` ASC) USING BTREE,
|
||||
UNIQUE INDEX `slug`(`slug` ASC) USING BTREE,
|
||||
INDEX `idx_slug`(`slug` ASC) USING BTREE COMMENT '按别名查询索引'
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '标签表' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '标签表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of tags
|
||||
-- ----------------------------
|
||||
INSERT IGNORE INTO `tags` (`id`, `name`, `slug`, `deleted_at`, `created_at`, `updated_at`) VALUES (1, 'Goravel', '', 0, 1768550858, 1768550858);
|
||||
INSERT INTO `tags` VALUES (1, 'Goravel', '', 0, 1768550858, 1768550858);
|
||||
INSERT INTO `tags` VALUES (2, '入门教程', '入门教程', 0, 1768809322, 1768809322);
|
||||
INSERT INTO `tags` VALUES (3, 'Golang框架', 'Golang框架', 0, 1768809341, 1768809341);
|
||||
INSERT INTO `tags` VALUES (4, '前后端分离', '前后端分离', 0, 1768809349, 1768809349);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for testimonials
|
||||
@@ -568,12 +687,6 @@ CREATE TABLE `user_access_logs` (
|
||||
-- ----------------------------
|
||||
-- Records of user_access_logs
|
||||
-- ----------------------------
|
||||
INSERT INTO `user_access_logs` VALUES (13, 0, '::1', 'Unknown', 6, 0, 20260116160835);
|
||||
INSERT INTO `user_access_logs` VALUES (14, 0, '::1', 'Unknown', 6, 0, 20260116165800);
|
||||
INSERT INTO `user_access_logs` VALUES (15, 0, '::1', 'Unknown', 6, 0, 20260119133107);
|
||||
INSERT INTO `user_access_logs` VALUES (16, 0, '::1', 'Unknown', 6, 0, 20260119133147);
|
||||
INSERT INTO `user_access_logs` VALUES (17, 0, '::1', 'Unknown', 6, 0, 20260119133216);
|
||||
INSERT INTO `user_access_logs` VALUES (18, 0, '::1', 'Internal', 6, 0, 1768801962);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for users
|
||||
@@ -602,9 +715,9 @@ CREATE TABLE `users` (
|
||||
-- ----------------------------
|
||||
-- Records of users
|
||||
-- ----------------------------
|
||||
INSERT IGNORE INTO `users` (`id`, `username`, `email`, `password_hash`, `role_id`, `role`, `is_active`, `deleted_at`, `created_at`, `updated_at`) VALUES (1, 'lq', 'liqiworker@gmail.com', '$2a$14$OqZ7svgz3S68yuuXTJWz1.0CuqI2i3WvwC1jIGBy4l0mcJW9Kqvy2', 1, 'admin', 1, 0, 1768291814, 1768538951);
|
||||
INSERT IGNORE INTO `users` (`id`, `username`, `email`, `password_hash`, `role_id`, `role`, `is_active`, `deleted_at`, `created_at`, `updated_at`) VALUES (2, 'editor', 'editor@example.com', '$2a$14$OqZ7svgz3S68yuuXTJWz1.0CuqI2i3WvwC1jIGBy4l0mcJW9Kqvy2', 2, 'editor', 1, 0, 1768291814, 1768538951);
|
||||
INSERT IGNORE INTO `users` (`id`, `username`, `email`, `password_hash`, `role_id`, `role`, `is_active`, `deleted_at`, `created_at`, `updated_at`) VALUES (5, 'cs', 'cs@nailaoyun.cn', '$2a$14$OqZ7svgz3S68yuuXTJWz1.0CuqI2i3WvwC1jIGBy4l0mcJW9Kqvy2', 3, 'viewer', 1, 0, 1768462115, 1768538951);
|
||||
INSERT INTO `users` VALUES (1, 'lq', 'liqiworker@gmail.com', '$2a$10$Bq6rv7714W3jGyXruYx4puXjflMTSkq2QM9kF54x9iZnk.0AD4B1G', 1, 'admin', 1, 0, 1768291814, 1768538951);
|
||||
INSERT INTO `users` VALUES (2, 'editor', 'editor@example.com', '$2a$14$OqZ7svgz3S68yuuXTJWz1.0CuqI2i3WvwC1jIGBy4l0mcJW9Kqvy2', 2, 'editor', 1, 0, 1768291814, 1768538951);
|
||||
INSERT INTO `users` VALUES (5, 'cs', 'cs@nailaoyun.cn', '$2a$14$OqZ7svgz3S68yuuXTJWz1.0CuqI2i3WvwC1jIGBy4l0mcJW9Kqvy2', 3, 'viewer', 1, 0, 1768462115, 1768538951);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for work_gallery
|
||||
@@ -621,15 +734,18 @@ CREATE TABLE `work_gallery` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_work_id`(`work_id` ASC) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品图库表' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品图库表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of work_gallery
|
||||
-- ----------------------------
|
||||
INSERT INTO `work_gallery` VALUES (1, 'nova', 'https://images.unsplash.com/photo-1642543492481-44e81e3914a7?q=80&w=2070', 1, 'Nova 交易平台首页', 0, 1768291937);
|
||||
INSERT INTO `work_gallery` VALUES (2, 'nova', 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?q=80&w=2070', 2, 'Nova 交易平台交易界面', 0, 1768291937);
|
||||
INSERT INTO `work_gallery` VALUES (1, 'nova', 'https://images.unsplash.com/photo-1642543492481-44e81e3914a7?q=80&w=2070', 1, 'Nova 交易平台首页', 1768825221, 1768291937);
|
||||
INSERT INTO `work_gallery` VALUES (2, 'nova', 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?q=80&w=2070', 2, 'Nova 交易平台交易界面', 1768825221, 1768291937);
|
||||
INSERT INTO `work_gallery` VALUES (3, 'archdaily', 'https://images.unsplash.com/photo-1503387762-592deb58ef4e?q=80&w=2089', 1, 'ArchDaily 网站首页', 0, 1768291937);
|
||||
INSERT INTO `work_gallery` VALUES (4, 'archdaily', 'https://images.unsplash.com/photo-1518005020951-ecc859466abc?q=80&w=1920', 2, 'ArchDaily 文章详情页', 0, 1768291937);
|
||||
INSERT INTO `work_gallery` VALUES (5, 'nova', 'https://images.unsplash.com/photo-1642543492481-44e81e3914a7?q=80&w=2070', 1, '', 0, 1768825221);
|
||||
INSERT INTO `work_gallery` VALUES (6, 'nova', 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?q=80&w=2070', 2, '', 0, 1768825221);
|
||||
INSERT INTO `work_gallery` VALUES (7, 'nova', '/uploads/2026/01/19/cc_upload_HiPDFg6oL0Q5ijfo693ce1f5eb57c_1768825204.jpg', 3, '', 0, 1768825221);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for work_tech_stack
|
||||
@@ -645,20 +761,25 @@ CREATE TABLE `work_tech_stack` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_work_id`(`work_id` ASC) USING BTREE,
|
||||
INDEX `idx_category`(`category` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品技术栈表' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '作品技术栈表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of work_tech_stack
|
||||
-- ----------------------------
|
||||
INSERT INTO `work_tech_stack` VALUES (1, 'nova', '前端层', 'React 18', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (2, 'nova', '前端层', 'TypeScript', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (3, 'nova', '前端层', 'D3.js', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (4, 'nova', '后端服务', 'Golang', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (5, 'nova', '后端服务', 'gRPC', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (1, 'nova', '前端层', 'React 18', 1768825220, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (2, 'nova', '前端层', 'TypeScript', 1768825220, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (3, 'nova', '前端层', 'D3.js', 1768825220, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (4, 'nova', '后端服务', 'Golang', 1768825220, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (5, 'nova', '后端服务', 'gRPC', 1768825220, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (6, 'archdaily', '核心前端', 'Vue 3', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (7, 'archdaily', '核心前端', 'Nuxt.js', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (8, 'archdaily', '核心前端', 'GSAP', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (9, 'archdaily', 'CMS', 'Strapi', 0, 1768291937);
|
||||
INSERT INTO `work_tech_stack` VALUES (10, 'nova', '前端层', 'React 18', 0, 1768825220);
|
||||
INSERT INTO `work_tech_stack` VALUES (11, 'nova', '前端层', 'TypeScript', 0, 1768825220);
|
||||
INSERT INTO `work_tech_stack` VALUES (12, 'nova', '前端层', 'D3.js', 0, 1768825220);
|
||||
INSERT INTO `work_tech_stack` VALUES (13, 'nova', '后端服务', 'Golang', 0, 1768825220);
|
||||
INSERT INTO `work_tech_stack` VALUES (14, 'nova', '后端服务', 'gRPC', 0, 1768825220);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for works
|
||||
@@ -671,6 +792,7 @@ CREATE TABLE `works` (
|
||||
`year` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '创作年份',
|
||||
`hero_img` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '作品主图URL',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '作品详细描述',
|
||||
`links` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '作品链接(JSON格式)',
|
||||
`is_featured` tinyint(1) NULL DEFAULT 0 COMMENT '是否为精选作品(0:否,1:是)',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
@@ -684,104 +806,7 @@ CREATE TABLE `works` (
|
||||
-- ----------------------------
|
||||
-- Records of works
|
||||
-- ----------------------------
|
||||
INSERT IGNORE INTO `works` VALUES ('archdaily', 'ArchDaily 网站重构', '建筑设计', '2022', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'ArchDaily 是全球最受欢迎的建筑网站之一。这次重构的目标是提升移动端体验。我们使用了 Nuxt 3 进行服务端渲染(SSR)。', 1, 0, 1768291814, 1768538952);
|
||||
INSERT IGNORE INTO `works` VALUES ('nova', 'Nova 交易平台', '金融科技', '2023', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'Nova 是一个专为机构交易员设计的高频交易终端。我们面临的最大挑战是如何在处理毫秒级市场数据的同时,保持界面的流畅响应。我们采用 Web Worker 来处理繁重的数据计算,避免阻塞主线程。', 1, 0, 1768291814, 1768538952);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for search_logs
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `search_logs`;
|
||||
CREATE TABLE `search_logs` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`keyword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '搜索关键词',
|
||||
`search_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '搜索类型: category/tag/column/keyword',
|
||||
`user_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户IP地址',
|
||||
`user_location` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户归属地',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_search_type`(`search_type` ASC) USING BTREE,
|
||||
INDEX `idx_created_at`(`created_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '搜索记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of search_logs
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for attachment_categories
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `attachment_categories`;
|
||||
CREATE TABLE `attachment_categories` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '分类描述',
|
||||
`sort_order` int UNSIGNED NULL DEFAULT 0 COMMENT '排序',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_sort_order`(`sort_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件分类表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of attachment_categories
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for oss_configs
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `oss_configs`;
|
||||
CREATE TABLE `oss_configs` (
|
||||
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '配置名称',
|
||||
`storage_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '存储类型: local/qcloud/aliyun/qiniu',
|
||||
`access_key` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Access Key (AES加密)',
|
||||
`secret_key` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Secret Key (AES加密)',
|
||||
`bucket` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '存储桶名称',
|
||||
`region` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '区域',
|
||||
`domain` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '访问域名',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否启用',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_storage_type`(`storage_type` ASC) USING BTREE,
|
||||
INDEX `idx_is_active`(`is_active` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OSS配置表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of oss_configs
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for attachments
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `attachments`;
|
||||
CREATE TABLE `attachments` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`category_id` int UNSIGNED NULL DEFAULT NULL COMMENT '附件分类ID',
|
||||
`original_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始文件名',
|
||||
`stored_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '存储文件名',
|
||||
`file_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件路径',
|
||||
`file_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件访问URL',
|
||||
`file_size` bigint UNSIGNED NOT NULL DEFAULT 0 COMMENT '文件大小(字节)',
|
||||
`file_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件类型: image/video/document/other',
|
||||
`mime_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'MIME类型',
|
||||
`storage_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'local' COMMENT '存储类型: local/qcloud/aliyun/qiniu',
|
||||
`oss_config_id` int UNSIGNED NULL DEFAULT NULL COMMENT 'OSS配置ID',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_category_id`(`category_id` ASC) USING BTREE,
|
||||
INDEX `idx_storage_type`(`storage_type` ASC) USING BTREE,
|
||||
INDEX `idx_file_type`(`file_type` ASC) USING BTREE,
|
||||
INDEX `idx_created_at`(`created_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '附件表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of attachments
|
||||
-- ----------------------------
|
||||
INSERT INTO `works` VALUES ('archdaily', 'ArchDaily 网站重构', '建筑设计', '2022', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'ArchDaily 是全球最受欢迎的建筑网站之一。这次重构的目标是提升移动端体验。我们使用了 Nuxt 3 进行服务端渲染(SSR)。', NULL, 0, 0, 1768291814, 1768812789);
|
||||
INSERT INTO `works` VALUES ('nova', 'Nova 交易平台', '金融科技', '2023', 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070', 'Nova 是一个专为机构交易员设计的高频交易终端。我们面临的最大挑战是如何在处理毫秒级市场数据的同时,保持界面的流畅响应。我们采用 Web Worker 来处理繁重的数据计算,避免阻塞主线程。', '{\"demo\":\"http://localhost:3000/works/nova\",\"github\":\"http://localhost:3000/works/nova\",\"live\":\"http://localhost:3000/works/nova\"}', 0, 0, 1768291814, 1768825220);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
@@ -78,6 +78,30 @@ func GetAttachmentByID(id uint) (*models.Attachment, error) {
|
||||
return &attachment, nil
|
||||
}
|
||||
|
||||
// UpdateAttachment 更新附件
|
||||
func UpdateAttachment(attachment *models.Attachment) error {
|
||||
updateData := map[string]interface{}{
|
||||
"updated_at": time.Now().Unix(),
|
||||
}
|
||||
|
||||
if attachment.CategoryID != nil {
|
||||
updateData["category_id"] = attachment.CategoryID
|
||||
} else {
|
||||
// 如果CategoryID为nil,设置为NULL
|
||||
updateData["category_id"] = nil
|
||||
}
|
||||
|
||||
err := config.DB.Model(&models.Attachment{}).
|
||||
Where("id = ? AND deleted_at = ?", attachment.ID, 0).
|
||||
Updates(updateData).Error
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error updating attachment: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAttachment 删除附件(软删除)
|
||||
func DeleteAttachment(id uint) error {
|
||||
err := config.DB.Model(&models.Attachment{}).
|
||||
@@ -213,6 +237,24 @@ func GetActiveOSSConfig(storageType string) (*models.OSSConfig, error) {
|
||||
return &ossConfig, nil
|
||||
}
|
||||
|
||||
// GetDefaultLocalOSSConfig 获取默认本地存储配置
|
||||
func GetDefaultLocalOSSConfig() (*models.OSSConfig, error) {
|
||||
var ossConfig models.OSSConfig
|
||||
err := config.DB.Model(&models.OSSConfig{}).
|
||||
Where("deleted_at = ? AND name = ? AND storage_type = ?", 0, "本地存储", "local").
|
||||
First(&ossConfig).Error
|
||||
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
log.Printf("Error getting default local OSS config: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ossConfig, nil
|
||||
}
|
||||
|
||||
// UpdateOSSConfig 更新OSS配置
|
||||
func UpdateOSSConfig(ossConfig *models.OSSConfig) error {
|
||||
err := config.DB.Model(&models.OSSConfig{}).
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
@@ -100,6 +101,17 @@ func BuildWorkResponse(work *models.Work) (*models.WorkResponse, error) {
|
||||
galleryImages = append(galleryImages, g.ImageURL)
|
||||
}
|
||||
|
||||
// 解析links JSON
|
||||
var linksData map[string]interface{}
|
||||
if work.Links != "" {
|
||||
if err := json.Unmarshal([]byte(work.Links), &linksData); err != nil {
|
||||
log.Printf("Error parsing links JSON: %v", err)
|
||||
linksData = map[string]interface{}{"live": ""}
|
||||
}
|
||||
} else {
|
||||
linksData = map[string]interface{}{"live": ""}
|
||||
}
|
||||
|
||||
// 获取下一个作品ID
|
||||
nextWorkID, err := GetNextWorkID(work.ID)
|
||||
if err != nil {
|
||||
@@ -116,10 +128,8 @@ func BuildWorkResponse(work *models.Work) (*models.WorkResponse, error) {
|
||||
Desc: work.Description,
|
||||
TechStack: techStackResponse,
|
||||
Gallery: galleryImages,
|
||||
Links: map[string]interface{}{
|
||||
"live": "#",
|
||||
},
|
||||
Next: nextWorkID,
|
||||
Links: linksData,
|
||||
Next: nextWorkID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -155,6 +165,78 @@ func GetNextWorkID(currentID string) (string, error) {
|
||||
return ids[index+1], nil
|
||||
}
|
||||
|
||||
// CreateWorkTechStack 创建作品技术栈
|
||||
func CreateWorkTechStack(workID string, techStack []map[string]interface{}) error {
|
||||
// 先删除旧的技术栈(软删除)
|
||||
err := config.DB.Model(&models.WorkTechStack{}).
|
||||
Where("work_id = ?", workID).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting old tech stack: %v", err)
|
||||
}
|
||||
|
||||
// 创建新的技术栈
|
||||
for _, categoryData := range techStack {
|
||||
category, ok := categoryData["category"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
items, ok := categoryData["items"].([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, item := range items {
|
||||
itemStr, ok := item.(string)
|
||||
if !ok || itemStr == "" {
|
||||
continue
|
||||
}
|
||||
techStackItem := models.WorkTechStack{
|
||||
WorkID: workID,
|
||||
Category: category,
|
||||
Item: itemStr,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
DeletedAt: 0,
|
||||
}
|
||||
if err := config.DB.Create(&techStackItem).Error; err != nil {
|
||||
log.Printf("Error creating tech stack item: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateWorkGallery 创建作品图库
|
||||
func CreateWorkGallery(workID string, gallery []string) error {
|
||||
// 先删除旧的图库(软删除)
|
||||
err := config.DB.Model(&models.WorkGallery{}).
|
||||
Where("work_id = ?", workID).
|
||||
Update("deleted_at", time.Now().Unix()).Error
|
||||
if err != nil {
|
||||
log.Printf("Error deleting old gallery: %v", err)
|
||||
}
|
||||
|
||||
// 创建新的图库
|
||||
for index, imageURL := range gallery {
|
||||
if imageURL == "" {
|
||||
continue
|
||||
}
|
||||
galleryItem := models.WorkGallery{
|
||||
WorkID: workID,
|
||||
ImageURL: imageURL,
|
||||
SortOrder: uint(index + 1),
|
||||
Description: "",
|
||||
CreatedAt: time.Now().Unix(),
|
||||
DeletedAt: 0,
|
||||
}
|
||||
if err := config.DB.Create(&galleryItem).Error; err != nil {
|
||||
log.Printf("Error creating gallery item: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateWork 创建作品
|
||||
func CreateWork(work *models.Work) error {
|
||||
err := config.DB.Create(work).Error
|
||||
@@ -167,17 +249,21 @@ func CreateWork(work *models.Work) error {
|
||||
|
||||
// UpdateWork 更新作品
|
||||
func UpdateWork(work *models.Work) error {
|
||||
updateData := map[string]interface{}{
|
||||
"title": work.Title,
|
||||
"category": work.Category,
|
||||
"year": work.Year,
|
||||
"hero_img": work.HeroImg,
|
||||
"description": work.Description,
|
||||
"is_featured": work.IsFeatured,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}
|
||||
if work.Links != "" {
|
||||
updateData["links"] = work.Links
|
||||
}
|
||||
err := config.DB.Model(&models.Work{}).
|
||||
Where("id = ? AND deleted_at = ?", work.ID, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"title": work.Title,
|
||||
"category": work.Category,
|
||||
"year": work.Year,
|
||||
"hero_img": work.HeroImg,
|
||||
"description": work.Description,
|
||||
"is_featured": work.IsFeatured,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
Updates(updateData).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating work: %v", err)
|
||||
return err
|
||||
|
||||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 40 KiB |
@@ -1,13 +1,22 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"github.com/qiniu/go-sdk/v7/auth/qbox"
|
||||
"github.com/qiniu/go-sdk/v7/storage"
|
||||
cos "github.com/tencentyun/cos-go-sdk-v5"
|
||||
)
|
||||
|
||||
// StorageType 存储类型
|
||||
@@ -23,11 +32,30 @@ const (
|
||||
// OSSConfig OSS配置
|
||||
type OSSConfig struct {
|
||||
StorageType string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
Bucket string
|
||||
Region string
|
||||
Domain string
|
||||
// 通用字段(向后兼容)
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
Bucket string
|
||||
Region string
|
||||
Domain string
|
||||
// 阿里云OSS专用字段
|
||||
OSSAccessKeyID string
|
||||
OSSAccessKeySecret string
|
||||
OSSEndpoint string
|
||||
OSSBucket string
|
||||
OSSDomain string
|
||||
// 腾讯云COS专用字段
|
||||
QCloudSecretID string
|
||||
QCloudSecretKey string
|
||||
QCloudRegion string
|
||||
QCloudBucket string
|
||||
QCloudDomain string
|
||||
// 七牛云专用字段
|
||||
QiniuAccessKey string
|
||||
QiniuSecretKey string
|
||||
QiniuBucket string
|
||||
QiniuRegion string
|
||||
QiniuDomain string
|
||||
}
|
||||
|
||||
// OSSUploader OSS上传接口
|
||||
@@ -45,14 +73,173 @@ func GetOSSUploader(config *OSSConfig) (OSSUploader, error) {
|
||||
BaseURL: "/uploads",
|
||||
}, nil
|
||||
case StorageQCloud:
|
||||
// TODO: 实现腾讯云COS上传
|
||||
return nil, fmt.Errorf("qcloud storage not implemented yet")
|
||||
// 优先使用专用字段,如果为空则使用通用字段(向后兼容)
|
||||
secretID := config.QCloudSecretID
|
||||
secretKey := config.QCloudSecretKey
|
||||
region := config.QCloudRegion
|
||||
bucket := config.QCloudBucket
|
||||
domain := config.QCloudDomain
|
||||
|
||||
if secretID == "" {
|
||||
secretID = config.AccessKey
|
||||
}
|
||||
if secretKey == "" {
|
||||
secretKey = config.SecretKey
|
||||
}
|
||||
if region == "" {
|
||||
region = config.Region
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = config.Bucket
|
||||
}
|
||||
if domain == "" {
|
||||
domain = config.Domain
|
||||
}
|
||||
|
||||
if secretID == "" || secretKey == "" || bucket == "" || region == "" {
|
||||
return nil, fmt.Errorf("qcloud config incomplete: secretID, secretKey, bucket, region are required")
|
||||
}
|
||||
|
||||
u, err := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", bucket, region))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid qcloud config: %v", err)
|
||||
}
|
||||
b := &cos.BaseURL{BucketURL: u}
|
||||
client := cos.NewClient(b, &http.Client{
|
||||
Transport: &cos.AuthorizationTransport{
|
||||
SecretID: secretID,
|
||||
SecretKey: secretKey,
|
||||
},
|
||||
})
|
||||
return &QCloudUploader{
|
||||
Client: client,
|
||||
Bucket: bucket,
|
||||
Domain: domain,
|
||||
}, nil
|
||||
case StorageAliyun:
|
||||
// TODO: 实现阿里云OSS上传
|
||||
return nil, fmt.Errorf("aliyun storage not implemented yet")
|
||||
// 优先使用专用字段,如果为空则使用通用字段(向后兼容)
|
||||
accessKeyID := config.OSSAccessKeyID
|
||||
accessKeySecret := config.OSSAccessKeySecret
|
||||
endpoint := config.OSSEndpoint
|
||||
bucket := config.OSSBucket
|
||||
domain := config.OSSDomain
|
||||
|
||||
if accessKeyID == "" {
|
||||
accessKeyID = config.AccessKey
|
||||
}
|
||||
if accessKeySecret == "" {
|
||||
accessKeySecret = config.SecretKey
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = config.Bucket
|
||||
}
|
||||
if domain == "" {
|
||||
domain = config.Domain
|
||||
}
|
||||
|
||||
if accessKeyID == "" || accessKeySecret == "" || bucket == "" {
|
||||
return nil, fmt.Errorf("aliyun config incomplete: accessKeyID, accessKeySecret, bucket are required")
|
||||
}
|
||||
|
||||
// 如果endpoint为空,从region构建(向后兼容)
|
||||
if endpoint == "" {
|
||||
region := config.Region
|
||||
if region == "" {
|
||||
return nil, fmt.Errorf("aliyun config incomplete: endpoint or region is required")
|
||||
}
|
||||
endpoint = fmt.Sprintf("https://oss-%s.aliyuncs.com", region)
|
||||
}
|
||||
|
||||
client, err := oss.New(endpoint, accessKeyID, accessKeySecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create aliyun oss client: %v", err)
|
||||
}
|
||||
ossBucket, err := client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get aliyun bucket: %v", err)
|
||||
}
|
||||
return &AliyunUploader{
|
||||
Bucket: ossBucket,
|
||||
Domain: domain,
|
||||
}, nil
|
||||
case StorageQiniu:
|
||||
// TODO: 实现七牛云上传
|
||||
return nil, fmt.Errorf("qiniu storage not implemented yet")
|
||||
// 优先使用专用字段,如果为空则使用通用字段(向后兼容)
|
||||
accessKey := config.QiniuAccessKey
|
||||
secretKey := config.QiniuSecretKey
|
||||
bucket := config.QiniuBucket
|
||||
region := config.QiniuRegion
|
||||
domain := config.QiniuDomain
|
||||
|
||||
if accessKey == "" {
|
||||
accessKey = config.AccessKey
|
||||
}
|
||||
if secretKey == "" {
|
||||
secretKey = config.SecretKey
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = config.Bucket
|
||||
}
|
||||
if domain == "" {
|
||||
domain = config.Domain
|
||||
}
|
||||
|
||||
if accessKey == "" || secretKey == "" || bucket == "" {
|
||||
return nil, fmt.Errorf("qiniu config incomplete: accessKey, secretKey, bucket are required")
|
||||
}
|
||||
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
// 根据region选择Zone(如果region为空,使用通用字段的region,否则默认华东)
|
||||
var zone *storage.Zone
|
||||
if region != "" {
|
||||
switch region {
|
||||
case "z0", "华东":
|
||||
zone = &storage.ZoneHuadong
|
||||
case "z1", "华北":
|
||||
zone = &storage.ZoneHuabei
|
||||
case "z2", "华南":
|
||||
zone = &storage.ZoneHuanan
|
||||
case "na0", "北美":
|
||||
zone = &storage.ZoneBeimei
|
||||
case "as0", "东南亚":
|
||||
zone = &storage.ZoneXinjiapo
|
||||
default:
|
||||
zone = &storage.ZoneHuadong // 默认华东
|
||||
}
|
||||
} else if config.Region != "" {
|
||||
// 使用通用字段的region
|
||||
switch config.Region {
|
||||
case "z0", "华东":
|
||||
zone = &storage.ZoneHuadong
|
||||
case "z1", "华北":
|
||||
zone = &storage.ZoneHuabei
|
||||
case "z2", "华南":
|
||||
zone = &storage.ZoneHuanan
|
||||
case "na0", "北美":
|
||||
zone = &storage.ZoneBeimei
|
||||
case "as0", "东南亚":
|
||||
zone = &storage.ZoneXinjiapo
|
||||
default:
|
||||
zone = &storage.ZoneHuadong
|
||||
}
|
||||
} else {
|
||||
zone = &storage.ZoneHuadong // 默认华东
|
||||
}
|
||||
|
||||
cfg := storage.Config{
|
||||
Zone: zone,
|
||||
UseHTTPS: true,
|
||||
UseCdnDomains: false,
|
||||
}
|
||||
formUploader := storage.NewFormUploader(&cfg)
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
return &QiniuUploader{
|
||||
FormUploader: formUploader,
|
||||
BucketManager: bucketManager,
|
||||
Bucket: bucket,
|
||||
Domain: domain,
|
||||
Mac: mac,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported storage type: %s", config.StorageType)
|
||||
}
|
||||
@@ -122,6 +309,160 @@ func (l *LocalUploader) Delete(filePath string) error {
|
||||
return os.Remove(filePath)
|
||||
}
|
||||
|
||||
// AliyunUploader 阿里云OSS上传器
|
||||
type AliyunUploader struct {
|
||||
Bucket *oss.Bucket
|
||||
Domain string
|
||||
}
|
||||
|
||||
// Upload 上传文件到阿里云OSS
|
||||
func (a *AliyunUploader) Upload(file multipart.File, filename string, size int64) (string, string, error) {
|
||||
// 生成唯一文件名
|
||||
ext := filepath.Ext(filename)
|
||||
timestamp := time.Now().Unix()
|
||||
randomStr := fmt.Sprintf("%d", timestamp)
|
||||
newFilename := fmt.Sprintf("%s_%s%s", strings.TrimSuffix(filename, ext), randomStr, ext)
|
||||
|
||||
// 按日期创建目录
|
||||
dateDir := time.Now().Format("2006/01/02")
|
||||
objectKey := fmt.Sprintf("%s/%s", dateDir, newFilename)
|
||||
|
||||
// 上传文件
|
||||
err := a.Bucket.PutObject(objectKey, file)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to upload to aliyun oss: %v", err)
|
||||
}
|
||||
|
||||
// 生成访问URL
|
||||
var fileURL string
|
||||
if a.Domain != "" {
|
||||
fileURL = fmt.Sprintf("%s/%s", strings.TrimSuffix(a.Domain, "/"), objectKey)
|
||||
} else {
|
||||
// 从endpoint中提取region,格式为 https://oss-region.aliyuncs.com
|
||||
endpoint := a.Bucket.Client.Config.Endpoint
|
||||
fileURL = fmt.Sprintf("%s/%s", endpoint, objectKey)
|
||||
}
|
||||
|
||||
return objectKey, fileURL, nil
|
||||
}
|
||||
|
||||
// Delete 删除阿里云OSS文件
|
||||
func (a *AliyunUploader) Delete(objectKey string) error {
|
||||
err := a.Bucket.DeleteObject(objectKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete from aliyun oss: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QCloudUploader 腾讯云COS上传器
|
||||
type QCloudUploader struct {
|
||||
Client *cos.Client
|
||||
Bucket string
|
||||
Domain string
|
||||
}
|
||||
|
||||
// Upload 上传文件到腾讯云COS
|
||||
func (q *QCloudUploader) Upload(file multipart.File, filename string, size int64) (string, string, error) {
|
||||
// 生成唯一文件名
|
||||
ext := filepath.Ext(filename)
|
||||
timestamp := time.Now().Unix()
|
||||
randomStr := fmt.Sprintf("%d", timestamp)
|
||||
newFilename := fmt.Sprintf("%s_%s%s", strings.TrimSuffix(filename, ext), randomStr, ext)
|
||||
|
||||
// 按日期创建目录
|
||||
dateDir := time.Now().Format("2006/01/02")
|
||||
objectKey := fmt.Sprintf("%s/%s", dateDir, newFilename)
|
||||
|
||||
// 上传文件
|
||||
_, err := q.Client.Object.Put(context.Background(), objectKey, file, nil)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to upload to qcloud cos: %v", err)
|
||||
}
|
||||
|
||||
// 生成访问URL
|
||||
var fileURL string
|
||||
if q.Domain != "" {
|
||||
fileURL = fmt.Sprintf("%s/%s", strings.TrimSuffix(q.Domain, "/"), objectKey)
|
||||
} else {
|
||||
// 使用BucketURL生成URL
|
||||
fileURL = fmt.Sprintf("%s/%s", q.Client.BaseURL.BucketURL.String(), objectKey)
|
||||
}
|
||||
|
||||
return objectKey, fileURL, nil
|
||||
}
|
||||
|
||||
// Delete 删除腾讯云COS文件
|
||||
func (q *QCloudUploader) Delete(objectKey string) error {
|
||||
_, err := q.Client.Object.Delete(context.Background(), objectKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete from qcloud cos: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QiniuUploader 七牛云上传器
|
||||
type QiniuUploader struct {
|
||||
FormUploader *storage.FormUploader
|
||||
BucketManager *storage.BucketManager
|
||||
Bucket string
|
||||
Domain string
|
||||
Mac *qbox.Mac
|
||||
}
|
||||
|
||||
// Upload 上传文件到七牛云
|
||||
func (q *QiniuUploader) Upload(file multipart.File, filename string, size int64) (string, string, error) {
|
||||
// 生成唯一文件名
|
||||
ext := filepath.Ext(filename)
|
||||
timestamp := time.Now().Unix()
|
||||
randomStr := fmt.Sprintf("%d", timestamp)
|
||||
newFilename := fmt.Sprintf("%s_%s%s", strings.TrimSuffix(filename, ext), randomStr, ext)
|
||||
|
||||
// 按日期创建目录
|
||||
dateDir := time.Now().Format("2006/01/02")
|
||||
key := fmt.Sprintf("%s/%s", dateDir, newFilename)
|
||||
|
||||
// 生成上传凭证
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: q.Bucket,
|
||||
}
|
||||
upToken := putPolicy.UploadToken(q.Mac)
|
||||
|
||||
// 读取文件内容
|
||||
fileData := make([]byte, size)
|
||||
_, err := file.Read(fileData)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to read file: %v", err)
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
ret := storage.PutRet{}
|
||||
err = q.FormUploader.Put(context.Background(), &ret, upToken, key, bytes.NewReader(fileData), size, nil)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to upload to qiniu: %v", err)
|
||||
}
|
||||
|
||||
// 生成访问URL
|
||||
var fileURL string
|
||||
if q.Domain != "" {
|
||||
fileURL = fmt.Sprintf("%s/%s", strings.TrimSuffix(q.Domain, "/"), key)
|
||||
} else {
|
||||
// 七牛云需要配置域名,如果没有配置则返回key
|
||||
fileURL = key
|
||||
}
|
||||
|
||||
return key, fileURL, nil
|
||||
}
|
||||
|
||||
// Delete 删除七牛云文件
|
||||
func (q *QiniuUploader) Delete(key string) error {
|
||||
err := q.BucketManager.Delete(q.Bucket, key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete from qiniu: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFileType 根据MIME类型判断文件类型
|
||||
func GetFileType(mimeType string) string {
|
||||
if strings.HasPrefix(mimeType, "image/") {
|
||||
|
||||