diff --git a/server/admin/common/captcha/captcha_ctl.go b/server/admin/common/captcha/captcha_ctl.go index 741cbeb..940d145 100644 --- a/server/admin/common/captcha/captcha_ctl.go +++ b/server/admin/common/captcha/captcha_ctl.go @@ -5,9 +5,10 @@ import ( "image/color" "x_admin/config" - config2 "github.com/TestsLing/aj-captcha-go/config" - constant "github.com/TestsLing/aj-captcha-go/const" - "github.com/TestsLing/aj-captcha-go/service" + config2 "x_admin/util/aj-captcha-go/config" + constant "x_admin/util/aj-captcha-go/const" + "x_admin/util/aj-captcha-go/service" + "github.com/gin-gonic/gin" "github.com/go-redis/redis/v9" ) diff --git a/server/go.mod b/server/go.mod index 82ebb58..7522f7b 100644 --- a/server/go.mod +++ b/server/go.mod @@ -3,14 +3,15 @@ module x_admin go 1.21 require ( - github.com/TestsLing/aj-captcha-go v0.0.0-20221117062708-1abcf4c62792 github.com/fatih/structs v1.1.0 github.com/gin-contrib/cors v1.4.0 github.com/gin-gonic/gin v1.9.1 github.com/go-playground/locales v0.14.1 github.com/go-playground/universal-translator v0.18.1 github.com/go-playground/validator/v10 v10.16.0 + github.com/go-redis/redis/v8 v8.11.5 github.com/go-redis/redis/v9 v9.0.0-rc.2 + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/google/uuid v1.4.0 github.com/jinzhu/copier v0.4.0 github.com/shirou/gopsutil v3.21.11+incompatible @@ -19,9 +20,11 @@ require ( github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.2 github.com/ua-parser/uap-go v0.0.0-20230823213814-f77b3e91e9dc + github.com/xuri/excelize/v2 v2.8.0 go.uber.org/zap v1.26.0 - gorm.io/driver/mysql v1.5.2 - gorm.io/gorm v1.25.5 + golang.org/x/image v0.15.0 + gorm.io/driver/mysql v1.5.4 + gorm.io/gorm v1.25.7 ) require ( @@ -40,10 +43,8 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/swag v0.22.4 // indirect - github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -76,14 +77,12 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca // indirect - github.com/xuri/excelize/v2 v2.8.0 // indirect github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.6.0 // indirect golang.org/x/crypto v0.16.0 // indirect golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect - golang.org/x/image v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/server/go.sum b/server/go.sum index eeb3cdb..3ee049a 100644 --- a/server/go.sum +++ b/server/go.sum @@ -40,14 +40,11 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/TestsLing/aj-captcha-go v0.0.0-20221117062708-1abcf4c62792 h1:JrIVYRSZCe3yQuOvKUbyAMI+PEw4DUfuoUQR/H+0Kcc= -github.com/TestsLing/aj-captcha-go v0.0.0-20221117062708-1abcf4c62792/go.mod h1:PZU6xNk8I+Z9NR5rZhlQzG/BkrBdA94ptoiNTle/8tI= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= @@ -81,8 +78,6 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= @@ -136,7 +131,6 @@ github.com/go-redis/redis/v9 v9.0.0-rc.2/go.mod h1:cgBknjwcBJa2prbnuHH/4k/Mlj4r0 github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -168,7 +162,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -198,7 +191,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= @@ -213,7 +205,6 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= @@ -266,19 +257,10 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= @@ -406,10 +388,9 @@ golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mg golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= -golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= -golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= +golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -437,7 +418,6 @@ golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -458,7 +438,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -468,7 +447,6 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -499,7 +477,6 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -508,11 +485,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -533,7 +507,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -541,7 +514,6 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -616,7 +588,6 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -717,7 +688,6 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= @@ -727,15 +697,12 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -743,11 +710,11 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C 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.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= -gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= -gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/driver/mysql v1.5.4 h1:igQmHfKcbaTVyAIHNhhB888vvxh8EdQ2uSUT0LPcBso= +gorm.io/driver/mysql v1.5.4/go.mod h1:9rYxJph/u9SWkWc9yY4XJ1F/+xO0S/ChOmbk3+Z5Tvs= +gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/server/util/aj-captcha-go/config/config.go b/server/util/aj-captcha-go/config/config.go new file mode 100644 index 0000000..3c15ac8 --- /dev/null +++ b/server/util/aj-captcha-go/config/config.go @@ -0,0 +1,120 @@ +package config + +import ( + "errors" + "image/color" + "strings" + constant "x_admin/util/aj-captcha-go/const" +) + +// WatermarkConfig 水印设置 +type WatermarkConfig struct { + FontSize int `yaml:"fontSize"` + Color color.RGBA `yaml:"color"` + Text string `yaml:"text"` +} + +type BlockPuzzleConfig struct { + // 校验时 容错偏移量 + Offset int `yaml:"offset"` +} + +type ClickWordConfig struct { + FontSize int `yaml:"fontSize"` + FontNum int `yaml:"fontNum"` +} + +// RedisConfig redis配置选项 +type RedisConfig struct { + //redis单机或者集群访问地址 + DBAddress []string `yaml:"dbAddress"` + //最大空闲连接数 + DBMaxIdle int `yaml:"dbMaxIdle"` + //最大连接数 + DBMaxActive int `yaml:"dbMaxActive"` + //redis表示空闲连接保活时间 + DBIdleTimeout int `yaml:"dbIdleTimeout"` + //redis用户 + DBUserName string `yaml:"dbUserName"` + //redis密码 + DBPassWord string `yaml:"dbPassWord"` + //是否使用redis集群 + EnableCluster bool `yaml:"enableCluster"` + //单机模式下使用redis的指定库,比如:0,1,2,3等等,默认为0 + DB int `yaml:"db"` +} + +type Config struct { + Watermark *WatermarkConfig `yaml:"watermark"` + ClickWord *ClickWordConfig `yaml:"clickWord"` + BlockPuzzle *BlockPuzzleConfig `yaml:"blockPuzzle"` + // 验证码使用的缓存类型 + CacheType string `yaml:"cacheType"` + CacheExpireSec int `yaml:"cacheExpireSec"` + // 项目的绝对路径: 图片、字体等 + ResourcePath string `yaml:"resourcePath"` +} + +func NewConfig() *Config { + return &Config{ + //可以为redis类型缓存RedisCacheKey,也可以为内存MemCacheKey + CacheType: constant.MemCacheKey, + Watermark: &WatermarkConfig{ + FontSize: 12, + Color: color.RGBA{R: 255, G: 255, B: 255, A: 255}, + Text: "我的水印", + }, + ClickWord: &ClickWordConfig{ + FontSize: 25, + FontNum: 4, + }, + BlockPuzzle: &BlockPuzzleConfig{Offset: 10}, + CacheExpireSec: 2 * 60, // 缓存有效时间 + ResourcePath: "./", + } +} + +// BuildConfig 生成config配置 +func BuildConfig(cacheType, resourcePath string, waterConfig *WatermarkConfig, clickWordConfig *ClickWordConfig, + puzzleConfig *BlockPuzzleConfig, cacheExpireSec int) *Config { + if len(resourcePath) == 0 { + resourcePath = constant.DefaultResourceRoot + } + if len(cacheType) == 0 { + cacheType = constant.MemCacheKey + } else if strings.Compare(cacheType, constant.MemCacheKey) != 0 && + strings.Compare(cacheType, constant.RedisCacheKey) != 0 { + panic(errors.New("cache type not support")) + return nil + } + if cacheExpireSec == 0 { + cacheExpireSec = 2 * 60 + } + if nil == waterConfig { + waterConfig = &WatermarkConfig{ + FontSize: 12, + Color: color.RGBA{R: 255, G: 255, B: 255, A: 255}, + Text: constant.DefaultText, + } + } + if nil == clickWordConfig { + clickWordConfig = &ClickWordConfig{ + FontSize: 25, + FontNum: 4, + } + } + if nil == puzzleConfig { + puzzleConfig = &BlockPuzzleConfig{Offset: 10} + } + + return &Config{ + //可以为redis类型缓存RedisCacheKey,也可以为内存MemCacheKey + CacheType: cacheType, + Watermark: waterConfig, + ClickWord: clickWordConfig, + BlockPuzzle: puzzleConfig, + // 缓存有效时间 + CacheExpireSec: cacheExpireSec, + ResourcePath: resourcePath, + } +} diff --git a/server/util/aj-captcha-go/const/const.go b/server/util/aj-captcha-go/const/const.go new file mode 100644 index 0000000..5b47cab --- /dev/null +++ b/server/util/aj-captcha-go/const/const.go @@ -0,0 +1,33 @@ +package constant + +const ( + // CodeKeyPrefix 缓存key前缀 + CodeKeyPrefix = "RUNNING:CAPTCHA:%s" + + // BlockPuzzleCaptcha 滑动验证码服务标识 + BlockPuzzleCaptcha = "blockPuzzle" + + // ClickWordCaptcha 点击验证码服务标识 + ClickWordCaptcha = "clickWord" + + // MemCacheKey 内存缓存标识 + MemCacheKey = "mem" + // RedisCacheKey redis缓存标识 + RedisCacheKey = "redis" + + // DefaultFont 字体文件地址 + DefaultFont = "/resources/fonts/WenQuanZhengHei.ttf" + // DefaultResourceRoot 默认根目录 + DefaultResourceRoot = "./" + // DefaultText 默认水印显示文字 + DefaultText = "我的水印" +) + +const ( + // DefaultTemplateImageDirectory 滑动模板图文件目录地址 + DefaultTemplateImageDirectory = "/resources/defaultImages/jigsaw/slidingBlock" + // DefaultBackgroundImageDirectory 背景图片目录地址 + DefaultBackgroundImageDirectory = "/resources/defaultImages/jigsaw/original" + // DefaultClickBackgroundImageDirectory 点击背景图默认地址 + DefaultClickBackgroundImageDirectory = "/resources/defaultImages/pic-click" +) diff --git a/server/util/aj-captcha-go/model/vo/point_vo.go b/server/util/aj-captcha-go/model/vo/point_vo.go new file mode 100644 index 0000000..c29e573 --- /dev/null +++ b/server/util/aj-captcha-go/model/vo/point_vo.go @@ -0,0 +1,37 @@ +package vo + +import ( + "encoding/json" + "math" +) + +type PointVO struct { + X int `json:"x,float64"` + Y int `json:"y,float64"` + SecretKey string +} + +func (p *PointVO) SetSecretKey(secretKey string) { + p.SecretKey = secretKey +} + +func NewPointVO(x int, y int) *PointVO { + return &PointVO{X: x, Y: y} +} + +func (p *PointVO) UnmarshalJSON(data []byte) error { + clientPoint := struct { + X float64 `json:"x,float64"` + Y float64 `json:"y,float64"` + SecretKey string + }{} + + if err := json.Unmarshal(data, &clientPoint); err != nil { + return err + } + + p.Y = int(math.Floor(clientPoint.Y)) + p.X = int(math.Floor(clientPoint.X)) + p.SecretKey = clientPoint.SecretKey + return nil +} diff --git a/server/util/aj-captcha-go/service/block_puzzle_captcha_service.go b/server/util/aj-captcha-go/service/block_puzzle_captcha_service.go new file mode 100644 index 0000000..a64b7b7 --- /dev/null +++ b/server/util/aj-captcha-go/service/block_puzzle_captcha_service.go @@ -0,0 +1,233 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "math" + constant "x_admin/util/aj-captcha-go/const" + "x_admin/util/aj-captcha-go/model/vo" + "x_admin/util/aj-captcha-go/util" + img "x_admin/util/aj-captcha-go/util/image" + + "golang.org/x/image/colornames" +) + +type BlockPuzzleCaptchaService struct { + point vo.PointVO + factory *CaptchaServiceFactory +} + +func NewBlockPuzzleCaptchaService(factory *CaptchaServiceFactory) *BlockPuzzleCaptchaService { + // 初始化静态资源 + img.SetUp(factory.config.ResourcePath) + + return &BlockPuzzleCaptchaService{ + factory: factory, + } +} + +// Get 获取验证码图片信息 +func (b *BlockPuzzleCaptchaService) Get() (map[string]interface{}, error) { + + // 初始化背景图片 + backgroundImage := img.GetBackgroundImage() + + // 为背景图片设置水印 + backgroundImage.SetText(b.factory.config.Watermark.Text, b.factory.config.Watermark.FontSize, b.factory.config.Watermark.Color) + + // 初始化模板图片 + templateImage := img.GetTemplateImage() + + // 构造前端所需图片 + b.pictureTemplatesCut(backgroundImage, templateImage) + + originalImageBase64, err := backgroundImage.Base64() + jigsawImageBase64, err := templateImage.Base64() + + if err != nil { + return nil, err + } + + data := make(map[string]interface{}) + data["originalImageBase64"] = originalImageBase64 + data["jigsawImageBase64"] = jigsawImageBase64 + data["secretKey"] = b.point.SecretKey + data["token"] = util.GetUuid() + + codeKey := fmt.Sprintf(constant.CodeKeyPrefix, data["token"]) + jsonPoint, err := json.Marshal(b.point) + if err != nil { + log.Printf("point json Marshal err: %v", err) + return nil, err + } + + b.factory.GetCache().Set(codeKey, string(jsonPoint), b.factory.config.CacheExpireSec) + + return data, nil +} + +func (b *BlockPuzzleCaptchaService) pictureTemplatesCut(backgroundImage *util.ImageUtil, templateImage *util.ImageUtil) { + // 生成拼图坐标点 + b.generateJigsawPoint(backgroundImage, templateImage) + // 裁剪模板图 + b.cutByTemplate(backgroundImage, templateImage, b.point.X, 0) + + // 插入干扰图 + for { + newTemplateImage := img.GetTemplateImage() + if newTemplateImage.Src != templateImage.Src { + offsetX := util.RandomInt(0, backgroundImage.Width-newTemplateImage.Width-5) + if math.Abs(float64(newTemplateImage.Width-offsetX)) > float64(newTemplateImage.Width/2) { + b.interferenceByTemplate(backgroundImage, newTemplateImage, offsetX, b.point.Y) + break + } + } + } +} + +// 插入干扰图 +func (b *BlockPuzzleCaptchaService) interferenceByTemplate(backgroundImage *util.ImageUtil, templateImage *util.ImageUtil, x1 int, y1 int) { + xLength := templateImage.Width + yLength := templateImage.Height + + for x := 0; x < xLength; x++ { + for y := 0; y < yLength; y++ { + // 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中 + isOpacity := templateImage.IsOpacity(x, y) + + // 当前模板像素在背景图中的位置 + backgroundX := x + x1 + backgroundY := y + y1 + + // 当不为透明时 + if !isOpacity { + // 背景图区域模糊 + backgroundImage.VagueImage(backgroundX, backgroundY) + } + + //防止数组越界判断 + if x == (xLength-1) || y == (yLength-1) { + continue + } + + rightOpacity := templateImage.IsOpacity(x+1, y) + downOpacity := templateImage.IsOpacity(x, y+1) + + //描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色 + if (isOpacity && !rightOpacity) || (!isOpacity && rightOpacity) || (isOpacity && !downOpacity) || (!isOpacity && downOpacity) { + backgroundImage.RgbaImage.SetRGBA(backgroundX, backgroundY, colornames.White) + } + } + } +} + +func (b *BlockPuzzleCaptchaService) cutByTemplate(backgroundImage *util.ImageUtil, templateImage *util.ImageUtil, x1, y1 int) { + xLength := templateImage.Width + yLength := templateImage.Height + + for x := 0; x < xLength; x++ { + for y := 0; y < yLength; y++ { + // 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中 + isOpacity := templateImage.IsOpacity(x, y) + + // 当前模板像素在背景图中的位置 + backgroundX := x + x1 + backgroundY := y + y1 + + // 当不为透明时 + if !isOpacity { + // 获取原图像素 + backgroundRgba := backgroundImage.RgbaImage.RGBAAt(backgroundX, backgroundY) + // 将原图的像素扣到模板图上 + templateImage.SetPixel(backgroundRgba, x, y) + // 背景图区域模糊 + backgroundImage.VagueImage(backgroundX, backgroundY) + } + + //防止数组越界判断 + if x == (xLength-1) || y == (yLength-1) { + continue + } + + rightOpacity := templateImage.IsOpacity(x+1, y) + downOpacity := templateImage.IsOpacity(x, y+1) + + //描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色 + if (isOpacity && !rightOpacity) || (!isOpacity && rightOpacity) || (isOpacity && !downOpacity) || (!isOpacity && downOpacity) { + templateImage.RgbaImage.SetRGBA(x, y, colornames.White) + backgroundImage.RgbaImage.SetRGBA(backgroundX, backgroundY, colornames.White) + } + } + } +} + +// 生成模板图在背景图中的随机坐标点 +func (b *BlockPuzzleCaptchaService) generateJigsawPoint(backgroundImage *util.ImageUtil, templateImage *util.ImageUtil) { + widthDifference := backgroundImage.Width - templateImage.Width + heightDifference := backgroundImage.Height - templateImage.Height + + x, y := 0, 0 + + if widthDifference <= 0 { + x = 5 + } else { + x = util.RandomInt(100, widthDifference-100) + } + if heightDifference <= 0 { + y = 5 + } else { + y = util.RandomInt(5, heightDifference) + } + point := vo.PointVO{X: x, Y: y} + point.SetSecretKey(util.RandString(16)) + b.point = point +} + +func (b *BlockPuzzleCaptchaService) Check(token string, pointJson string) error { + cache := b.factory.GetCache() + + codeKey := fmt.Sprintf(constant.CodeKeyPrefix, token) + + cachePointInfo := cache.Get(codeKey) + + if cachePointInfo == "" { + return errors.New("验证码已失效") + } + + // 解析结构体 + cachePoint := &vo.PointVO{} + userPoint := &vo.PointVO{} + err := json.Unmarshal([]byte(cachePointInfo), cachePoint) + + if err != nil { + return err + } + + // 解密前端传递过来的数据 + userPointJson := util.AesDecrypt(pointJson, cachePoint.SecretKey) + + err = json.Unmarshal([]byte(userPointJson), userPoint) + + if err != nil { + return err + } + + // 校验两个点是否符合 + if math.Abs(float64(cachePoint.X-userPoint.X)) <= float64(b.factory.config.BlockPuzzle.Offset) && cachePoint.Y == userPoint.Y { + return nil + } + + return errors.New("验证失败") +} + +func (b *BlockPuzzleCaptchaService) Verification(token string, pointJson string) error { + err := b.Check(token, pointJson) + if err != nil { + return err + } + codeKey := fmt.Sprintf(constant.CodeKeyPrefix, token) + b.factory.GetCache().Delete(codeKey) + return nil +} diff --git a/server/util/aj-captcha-go/service/captcha_cache_interface.go b/server/util/aj-captcha-go/service/captcha_cache_interface.go new file mode 100644 index 0000000..af4bbfb --- /dev/null +++ b/server/util/aj-captcha-go/service/captcha_cache_interface.go @@ -0,0 +1,10 @@ +package service + +type CaptchaCacheInterface interface { + Get(key string) string + Set(key string, val string, expiresInSeconds int) + Delete(key string) + Exists(key string) bool + GetType() string + Increment(key string, val int) int +} diff --git a/server/util/aj-captcha-go/service/captcha_interface.go b/server/util/aj-captcha-go/service/captcha_interface.go new file mode 100644 index 0000000..61e2a5d --- /dev/null +++ b/server/util/aj-captcha-go/service/captcha_interface.go @@ -0,0 +1,13 @@ +package service + +type CaptchaInterface interface { + + // Get 获取验证码 + Get() (map[string]interface{}, error) + + // Check 核对验证码 + Check(token string, pointJson string) error + + // Verification 二次校验验证码(后端) + Verification(token string, pointJson string) error +} diff --git a/server/util/aj-captcha-go/service/captcha_service_factory.go b/server/util/aj-captcha-go/service/captcha_service_factory.go new file mode 100644 index 0000000..a296a48 --- /dev/null +++ b/server/util/aj-captcha-go/service/captcha_service_factory.go @@ -0,0 +1,58 @@ +package service + +import ( + "log" + "sync" + configIns "x_admin/util/aj-captcha-go/config" +) + +// CaptchaServiceFactory 验证码服务工厂 +type CaptchaServiceFactory struct { + config *configIns.Config + ServiceMap map[string]CaptchaInterface + ServiceLock sync.RWMutex + + CacheMap map[string]CaptchaCacheInterface + CacheLock sync.RWMutex +} + +func NewCaptchaServiceFactory(config *configIns.Config) *CaptchaServiceFactory { + + factory := &CaptchaServiceFactory{ + ServiceMap: make(map[string]CaptchaInterface), + CacheMap: make(map[string]CaptchaCacheInterface), + config: config, + } + return factory +} + +func (c *CaptchaServiceFactory) GetCache() CaptchaCacheInterface { + key := c.config.CacheType + c.CacheLock.RLock() + defer c.CacheLock.RUnlock() + if _, ok := c.CacheMap[key]; !ok { + log.Printf("未注册%s类型的Cache", key) + } + return c.CacheMap[key] +} + +func (c *CaptchaServiceFactory) RegisterCache(key string, cacheInterface CaptchaCacheInterface) { + c.CacheLock.Lock() + defer c.CacheLock.Unlock() + c.CacheMap[key] = cacheInterface +} + +func (c *CaptchaServiceFactory) RegisterService(key string, service CaptchaInterface) { + c.ServiceLock.Lock() + defer c.ServiceLock.Unlock() + c.ServiceMap[key] = service +} + +func (c *CaptchaServiceFactory) GetService(key string) CaptchaInterface { + c.ServiceLock.RLock() + defer c.ServiceLock.RUnlock() + if _, ok := c.ServiceMap[key]; !ok { + log.Printf("未注册%s类型的Service", key) + } + return c.ServiceMap[key] +} diff --git a/server/util/aj-captcha-go/service/click_word_captcha_service.go b/server/util/aj-captcha-go/service/click_word_captcha_service.go new file mode 100644 index 0000000..a3de73d --- /dev/null +++ b/server/util/aj-captcha-go/service/click_word_captcha_service.go @@ -0,0 +1,181 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + "log" + constant "x_admin/util/aj-captcha-go/const" + "x_admin/util/aj-captcha-go/model/vo" + "x_admin/util/aj-captcha-go/util" + img "x_admin/util/aj-captcha-go/util/image" +) + +const ( + TEXT = "的一了是我不在人们有来他这上着个地到大里说就去子得也和那要下看天时过出小么起你都把好还多没为又可家学只以主会样年想生同老中十从自面前头道它后然走很像见两用她国动进成回什边作对开而己些现山民候经发工向事命给长水几义三声于高手知理眼志点心战二问但身方实吃做叫当住听革打呢真全才四已所敌之最光产情路分总条白话东席次亲如被花口放儿常气五第使写军吧文运再果怎定许快明行因别飞外树物活部门无往船望新带队先力完却站代员机更九您每风级跟笑啊孩万少直意夜比阶连车重便斗马哪化太指变社似士者干石满日决百原拿群究各六本思解立河村八难早论吗根共让相研今其书坐接应关信觉步反处记将千找争领或师结块跑谁草越字加脚紧爱等习阵怕月青半火法题建赶位唱海七女任件感准张团屋离色脸片科倒睛利世刚且由送切星导晚表够整认响雪流未场该并底深刻平伟忙提确近亮轻讲农古黑告界拉名呀土清阳照办史改历转画造嘴此治北必服雨穿内识验传业菜爬睡兴形量咱观苦体众通冲合破友度术饭公旁房极南枪读沙岁线野坚空收算至政城劳落钱特围弟胜教热展包歌类渐强数乡呼性音答哥际旧神座章帮啦受系令跳非何牛取入岸敢掉忽种装顶急林停息句区衣般报叶压慢叔背细" +) + +type ClickWordCaptchaService struct { + factory *CaptchaServiceFactory +} + +func NewClickWordCaptchaService(factory *CaptchaServiceFactory) *ClickWordCaptchaService { + img.SetUp(factory.config.ResourcePath) + return &ClickWordCaptchaService{factory: factory} +} + +func (c *ClickWordCaptchaService) Get() (map[string]interface{}, error) { + // 初始化背景图片 + backgroundImage := img.GetClickBackgroundImage() + + pointList, wordList, err := c.getImageData(backgroundImage) + if err != nil { + return nil, err + } + + originalImageBase64, err := backgroundImage.Base64() + + if err != nil { + return nil, err + } + + data := make(map[string]interface{}) + data["originalImageBase64"] = originalImageBase64 + data["wordList"] = wordList + data["secretKey"] = pointList[0].SecretKey + data["token"] = util.GetUuid() + + codeKey := fmt.Sprintf(constant.CodeKeyPrefix, data["token"]) + jsonPoint, err := json.Marshal(pointList) + if err != nil { + log.Printf("point json Marshal err: %v", err) + return nil, err + } + + c.factory.GetCache().Set(codeKey, string(jsonPoint), c.factory.config.CacheExpireSec) + return data, nil +} + +func (c *ClickWordCaptchaService) Check(token string, pointJson string) error { + cache := c.factory.GetCache() + codeKey := fmt.Sprintf(constant.CodeKeyPrefix, token) + + cachePointInfo := cache.Get(codeKey) + + if cachePointInfo == "" { + return errors.New("验证码已失效") + } + + // 解析结构体 + var cachePoint []vo.PointVO + + var userPoint []vo.PointVO + + err := json.Unmarshal([]byte(cachePointInfo), &cachePoint) + + if err != nil { + return err + } + + // 解密前端传递过来的数据 + userPointJson := util.AesDecrypt(pointJson, cachePoint[0].SecretKey) + + err = json.Unmarshal([]byte(userPointJson), &userPoint) + + if err != nil { + return err + } + + for i, pointVO := range cachePoint { + targetPoint := userPoint[i] + fontsize := c.factory.config.ClickWord.FontSize + if targetPoint.X-fontsize > pointVO.X || targetPoint.X > pointVO.X+fontsize || targetPoint.Y-fontsize > pointVO.Y || targetPoint.Y > pointVO.Y+fontsize { + return errors.New("验证失败") + } + } + + return nil +} + +func (c *ClickWordCaptchaService) Verification(token string, pointJson string) error { + err := c.Check(token, pointJson) + if err != nil { + return err + } + codeKey := fmt.Sprintf(constant.CodeKeyPrefix, token) + c.factory.GetCache().Delete(codeKey) + return nil +} + +func (c *ClickWordCaptchaService) getImageData(image *util.ImageUtil) ([]vo.PointVO, []string, error) { + wordCount := c.factory.config.ClickWord.FontNum + + // 某个字不参与校验 + num := util.RandomInt(1, wordCount) + currentWord := c.getRandomWords(wordCount) + + var pointList []vo.PointVO + var wordList []string + + i := 0 + + // 构建本次的 secret + key := util.RandString(16) + + for _, s := range currentWord { + point := c.randomWordPoint(image.Width, image.Height, i, wordCount) + point.SetSecretKey(key) + // 随机设置文字 TODO 角度未设置 + err := image.SetArtText(s, c.factory.config.ClickWord.FontSize, point) + + if err != nil { + return nil, nil, err + } + + if (num - 1) != i { + pointList = append(pointList, point) + wordList = append(wordList, s) + } + i++ + } + return pointList, wordList, nil +} + +// getRandomWords 获取随机文件 +func (c *ClickWordCaptchaService) getRandomWords(count int) []string { + runesArray := []rune(TEXT) + size := len(runesArray) + + set := make(map[string]bool) + var wordList []string + + for { + word := runesArray[util.RandomInt(0, size-1)] + set[string(word)] = true + if len(set) >= count { + for str, _ := range set { + wordList = append(wordList, str) + } + break + } + } + return wordList +} + +func (c *ClickWordCaptchaService) randomWordPoint(width int, height int, i int, count int) vo.PointVO { + avgWidth := width / (count + 1) + fontSizeHalf := c.factory.config.ClickWord.FontSize / 2 + + var x, y int + if avgWidth < fontSizeHalf { + x = util.RandomInt(1+fontSizeHalf, width) + } else { + if i == 0 { + x = util.RandomInt(1+fontSizeHalf, avgWidth*(i+1)-fontSizeHalf) + } else { + x = util.RandomInt(avgWidth*i+fontSizeHalf, avgWidth*(i+1)-fontSizeHalf) + } + } + y = util.RandomInt(c.factory.config.ClickWord.FontSize, height-fontSizeHalf) + return vo.PointVO{X: x, Y: y} +} diff --git a/server/util/aj-captcha-go/service/mem_cache_service.go b/server/util/aj-captcha-go/service/mem_cache_service.go new file mode 100644 index 0000000..af5ceba --- /dev/null +++ b/server/util/aj-captcha-go/service/mem_cache_service.go @@ -0,0 +1,48 @@ +package service + +import ( + "strconv" + "x_admin/util/aj-captcha-go/util" +) + +type MemCacheService struct { + Cache *util.CacheUtil +} + +func NewMemCacheService(captchaCacheMaxNumber int) CaptchaCacheInterface { + return &MemCacheService{Cache: util.NewCacheUtil(captchaCacheMaxNumber)} +} + +func (l *MemCacheService) Get(key string) string { + return l.Cache.Get(key) +} + +func (l *MemCacheService) Set(key string, val string, expiresInSeconds int) { + l.Cache.Set(key, val, expiresInSeconds) +} + +func (l *MemCacheService) Delete(key string) { + l.Cache.Delete(key) +} + +func (l *MemCacheService) Exists(key string) bool { + return l.Cache.Exists(key) +} + +func (l *MemCacheService) GetType() string { + return "mem" +} + +func (l *MemCacheService) Increment(key string, val int) int { + cacheVal := l.Cache.Get(key) + num, err := strconv.Atoi(cacheVal) + if err != nil { + num = 0 + } + + ret := num + val + + l.Cache.Set(key, strconv.Itoa(ret), 0) + + return ret +} diff --git a/server/util/aj-captcha-go/service/redis_cache_service.go b/server/util/aj-captcha-go/service/redis_cache_service.go new file mode 100644 index 0000000..8405dc7 --- /dev/null +++ b/server/util/aj-captcha-go/service/redis_cache_service.go @@ -0,0 +1,49 @@ +package service + +import ( + "strconv" + "x_admin/util/aj-captcha-go/util" +) + +type RedisCacheService struct { + Cache *util.RedisUtil +} + +// NewConfigRedisCacheService 初始化自定义redis配置 +func NewConfigRedisCacheService(rdsAddr []string, dbUserName, dbPassword string, enableCluster bool, db int) CaptchaCacheInterface { + redisUtils := util.NewConfigRedisUtil(rdsAddr, dbUserName, dbPassword, enableCluster, db) + return &RedisCacheService{Cache: redisUtils} +} + +func (l *RedisCacheService) Get(key string) string { + return l.Cache.Get(key) +} + +func (l *RedisCacheService) Set(key string, val string, expiresInSeconds int) { + l.Cache.Set(key, val, expiresInSeconds) +} + +func (l *RedisCacheService) Delete(key string) { + l.Cache.Delete(key) +} + +func (l *RedisCacheService) Exists(key string) bool { + return l.Cache.Exists(key) +} + +func (l *RedisCacheService) GetType() string { + return "redis" +} + +func (l *RedisCacheService) Increment(key string, val int) int { + cacheVal := l.Cache.Get(key) + num, err := strconv.Atoi(cacheVal) + if err != nil { + num = 0 + } + + ret := num + val + + l.Cache.Set(key, strconv.Itoa(ret), 0) + return ret +} diff --git a/server/util/aj-captcha-go/util/aes_util.go b/server/util/aj-captcha-go/util/aes_util.go new file mode 100644 index 0000000..580e010 --- /dev/null +++ b/server/util/aj-captcha-go/util/aes_util.go @@ -0,0 +1,64 @@ +package util + +import ( + "bytes" + "crypto/aes" + "encoding/base64" +) + +func AesEncrypt(data, key string) string { + if key == "" { + return data + } + return base64.StdEncoding.EncodeToString(AesEncryptToBytes([]byte(data), []byte(key))) +} + +func AesEncryptToBytes(data, key []byte) []byte { + block, _ := aes.NewCipher(key) + data = PKCS5Padding(data, block.BlockSize()) + decrypted := make([]byte, len(data)) + size := block.BlockSize() + + for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size { + block.Encrypt(decrypted[bs:be], data[bs:be]) + } + + return decrypted +} + +func AesDecrypt(point string, key string) string { + encryptBytes, _ := base64.StdEncoding.DecodeString(point) + info := AESDecryptECB(encryptBytes, []byte(key)) + return string(info) +} + +func AESDecryptECB(data, key []byte) []byte { + block, _ := aes.NewCipher(key) + decrypted := make([]byte, len(data)) + size := block.BlockSize() + + for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size { + block.Decrypt(decrypted[bs:be], data[bs:be]) + } + + return PKCS5UnPadding(decrypted) +} + +// PKCS5UnPadding 删除pks5填充的尾部数据 +func PKCS5UnPadding(origData []byte) []byte { + // 1. 计算数据的总长度 + length := len(origData) + if length == 0 { + return origData + } + // 2. 根据填充的字节值得到填充的次数 + number := int(origData[length-1]) + // 3. 将尾部填充的number个字节去掉 + return origData[:(length - number)] +} + +func PKCS5Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} diff --git a/server/util/aj-captcha-go/util/cache_util.go b/server/util/aj-captcha-go/util/cache_util.go new file mode 100644 index 0000000..86d246f --- /dev/null +++ b/server/util/aj-captcha-go/util/cache_util.go @@ -0,0 +1,89 @@ +package util + +import ( + "log" + "strconv" + "sync" + "time" +) + +type CacheUtil struct { + Data map[string]string + DataRWLock sync.RWMutex + CaptchaCacheMaxNumber int +} + +func NewCacheUtil(captchaCacheMaxNumber int) *CacheUtil { + return &CacheUtil{ + Data: make(map[string]string), + CaptchaCacheMaxNumber: captchaCacheMaxNumber, + } +} + +func (l *CacheUtil) Exists(key string) bool { + l.DataRWLock.RLock() + timeVal := l.Data[key+"_HoldTime"] + cacheHoldTime, err := strconv.ParseInt(timeVal, 10, 64) + l.DataRWLock.RUnlock() + + if err != nil { + return false + } + + if cacheHoldTime == 0 { + return true + } + + if cacheHoldTime < time.Now().Unix() { + l.Delete(key) + return false + } + return true +} + +func (l *CacheUtil) Get(key string) string { + + if l.Exists(key) { + l.DataRWLock.RLock() + val := l.Data[key] + l.DataRWLock.RUnlock() + + return val + } + + return "" +} + +func (l *CacheUtil) Set(key string, val string, expiresInSeconds int) { + + //设置阈值,达到即clear缓存 + if len(l.Data) >= l.CaptchaCacheMaxNumber*2 { + log.Println("CACHE_MAP达到阈值,clear map") + l.Clear() + } + + l.DataRWLock.Lock() + l.Data[key] = val + if expiresInSeconds > 0 { + // 缓存失效时间 + nowTime := time.Now().Unix() + int64(expiresInSeconds) + l.Data[key+"_HoldTime"] = strconv.FormatInt(nowTime, 10) + } else { + l.Data[key+"_HoldTime"] = strconv.FormatInt(0, 10) + } + + l.DataRWLock.Unlock() +} + +func (l *CacheUtil) Delete(key string) { + l.DataRWLock.Lock() + defer l.DataRWLock.Unlock() + delete(l.Data, key) + delete(l.Data, key+"_HoldTime") +} + +func (l *CacheUtil) Clear() { + for key, _ := range l.Data { + l.Delete(key) + } +} diff --git a/server/util/aj-captcha-go/util/font_util.go b/server/util/aj-captcha-go/util/font_util.go new file mode 100644 index 0000000..4229b35 --- /dev/null +++ b/server/util/aj-captcha-go/util/font_util.go @@ -0,0 +1,51 @@ +package util + +import ( + "github.com/golang/freetype" + "github.com/golang/freetype/truetype" + "io/ioutil" + "log" + "unicode" +) + +type FontUtil struct { + Src string +} + +// NewFontUtil 字体绝对路径 +func NewFontUtil(src string) *FontUtil { + return &FontUtil{Src: src} +} + +// GetFont 获取一个字体对象 +func (f *FontUtil) GetFont() *truetype.Font { + fontSourceBytes, err := ioutil.ReadFile(f.Src) + if err != nil { + log.Println("读取字体失败:", err) + } + + trueTypeFont, err := freetype.ParseFont(fontSourceBytes) + + if err != nil { + log.Println("解析字体失败:", err) + } + + return trueTypeFont +} + +func GetEnOrChLength(text string) int { + enCount, zhCount := 0, 0 + + for _, t := range text { + if unicode.Is(unicode.Han, t) { + zhCount++ + } else { + enCount++ + } + } + + chOffset := (25/2)*zhCount + 5 + enOffset := enCount * 8 + + return chOffset + enOffset +} diff --git a/server/util/aj-captcha-go/util/image/image.go b/server/util/aj-captcha-go/util/image/image.go new file mode 100644 index 0000000..4dbdd0d --- /dev/null +++ b/server/util/aj-captcha-go/util/image/image.go @@ -0,0 +1,78 @@ +package image + +import ( + "log" + "os" + "path/filepath" + constant "x_admin/util/aj-captcha-go/const" + "x_admin/util/aj-captcha-go/util" +) + +var backgroundImageArr []string +var clickBackgroundImageArr []string +var templateImageArr []string + +var resourceAbsPath string + +func SetUp(resourcePath string) { + resourceAbsPath = resourcePath + root := resourcePath + + //root := "/Users/skyline/go/src/aj-captcha-go" + backgroundImageRoot := root + constant.DefaultBackgroundImageDirectory + templateImageRoot := root + constant.DefaultTemplateImageDirectory + clickBackgroundImageRoot := root + constant.DefaultClickBackgroundImageDirectory + + err := filepath.Walk(backgroundImageRoot, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + backgroundImageArr = append(backgroundImageArr, path) + return nil + }) + + err = filepath.Walk(templateImageRoot, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + templateImageArr = append(templateImageArr, path) + return nil + }) + + err = filepath.Walk(clickBackgroundImageRoot, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + clickBackgroundImageArr = append(clickBackgroundImageArr, path) + return nil + }) + + if err != nil { + log.Printf("初始化resource目录失败,请检查该目录是否存在 err: %v", err) + } + +} + +func GetBackgroundImage() *util.ImageUtil { + max := len(backgroundImageArr) - 1 + if max <= 0 { + max = 1 + } + return util.NewImageUtil(backgroundImageArr[util.RandomInt(0, max)], resourceAbsPath+constant.DefaultFont) +} + +func GetTemplateImage() *util.ImageUtil { + max := len(templateImageArr) - 1 + if max <= 0 { + max = 1 + } + return util.NewImageUtil(templateImageArr[util.RandomInt(0, max)], resourceAbsPath+constant.DefaultFont) +} + +func GetClickBackgroundImage() *util.ImageUtil { + max := len(templateImageArr) - 1 + if max <= 0 { + max = 1 + } + return util.NewImageUtil(clickBackgroundImageArr[util.RandomInt(0, max)], resourceAbsPath+constant.DefaultFont) +} diff --git a/server/util/aj-captcha-go/util/image_util.go b/server/util/aj-captcha-go/util/image_util.go new file mode 100644 index 0000000..a78d9e1 --- /dev/null +++ b/server/util/aj-captcha-go/util/image_util.go @@ -0,0 +1,264 @@ +package util + +import ( + "bytes" + "encoding/base64" + "image" + "image/color" + "image/draw" + "image/png" + "log" + "os" + "path" + "path/filepath" + "runtime" + "strings" + "x_admin/util/aj-captcha-go/model/vo" + + "github.com/golang/freetype" +) + +type ImageUtil struct { + Src string + SrcImage image.Image + RgbaImage *image.RGBA + FontPath string + Width int + Height int +} + +// NewImageUtil src为绝对路径 +func NewImageUtil(src string, fontPath string) *ImageUtil { + srcImage := OpenPngImage(src) + + return &ImageUtil{Src: src, + SrcImage: srcImage, + RgbaImage: ImageToRGBA(srcImage), + Width: srcImage.Bounds().Dx(), + Height: srcImage.Bounds().Dy(), + FontPath: fontPath, + } +} + +// IsOpacity 该像素是否透明 +func (i *ImageUtil) IsOpacity(x, y int) bool { + A := i.RgbaImage.RGBAAt(x, y).A + + if float32(A) <= 125 { + return true + } + return false +} + +// DecodeImageToFile 将图片转换为新的文件 调试使用 +func (i *ImageUtil) DecodeImageToFile() { + filename := "drawImg.png" + file, err := os.Create(filename) + if err != nil { + log.Printf("创建 %s 失败 %v", filename, err) + } + + err = png.Encode(file, i.RgbaImage) + if err != nil { + log.Printf("png %s Encode 失败 %v", filename, err) + } + +} + +// SetText 为图片设置文字 +func (i *ImageUtil) SetText(text string, fontsize int, color color.RGBA) { + + x := float64(i.Width) - float64(GetEnOrChLength(text)) + y := float64(i.Height) - (25 / 2) + 7 + + font := NewFontUtil(i.FontPath) + + fc := freetype.NewContext() + // 设置屏幕每英寸的分辨率 + //fc.SetDPI(72) + // 设置用于绘制文本的字体 + fc.SetFont(font.GetFont()) + // 以磅为单位设置字体大小 + fc.SetFontSize(float64(fontsize)) + // 设置剪裁矩形以进行绘制 + fc.SetClip(i.RgbaImage.Bounds()) + // 设置目标图像 + fc.SetDst(i.RgbaImage) + // 设置绘制操作的源图像,通常为 image.Uniform + fc.SetSrc(image.NewUniform(color)) + // 设置水印地址 + pt := freetype.Pt(int(x), int(y)) + // 根据 Pt 的坐标值绘制给定的文本内容 + _, err := fc.DrawString(text, pt) + if err != nil { + log.Println("构造水印失败:", err) + } +} + +// SetArtText 为图片设置文字 +func (i *ImageUtil) SetArtText(text string, fontsize int, point vo.PointVO) error { + + font := NewFontUtil(i.FontPath) + + fc := freetype.NewContext() + // 设置屏幕每英寸的分辨率 + //fc.SetDPI(72) + // 设置用于绘制文本的字体 + fc.SetFont(font.GetFont()) + // 以磅为单位设置字体大小 + fc.SetFontSize(float64(fontsize)) + // 设置剪裁矩形以进行绘制 + fc.SetClip(i.RgbaImage.Bounds()) + // 设置目标图像 + fc.SetDst(i.RgbaImage) + // 设置绘制操作的源图像,通常为 image.Uniform + fc.SetSrc(image.NewUniform(color.RGBA{R: uint8(RandomInt(1, 200)), G: uint8(RandomInt(1, 200)), B: uint8(RandomInt(1, 200)), A: 255})) + // 设置水印地址 + pt := freetype.Pt(point.X, point.Y) + // 根据 Pt 的坐标值绘制给定的文本内容 + _, err := fc.DrawString(text, pt) + if err != nil { + log.Printf("构造水印失败 err: %v", err) + return err + } + + return nil +} + +// SetPixel 为像素设置颜色 +func (i *ImageUtil) SetPixel(rgba color.RGBA, x, y int) { + i.RgbaImage.SetRGBA(x, y, rgba) +} + +// Base64 为像素设置颜色 +func (i *ImageUtil) Base64() (string, error) { + // 开辟一个新的空buff + var buf bytes.Buffer + // img写入到buff + err := png.Encode(&buf, i.RgbaImage) + + if err != nil { + log.Printf("img写入buf失败 err: %v", err) + return "", err + } + + //开辟存储空间 + dist := make([]byte, buf.Cap()+buf.Len()) + // buff转成base64 + base64.StdEncoding.Encode(dist, buf.Bytes()) + return string(dist), nil +} + +// VagueImage 模糊区域 +func (i *ImageUtil) VagueImage(x int, y int) { + var red uint32 + var green uint32 + var blue uint32 + var alpha uint32 + + points := [8][2]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}} + + for _, point := range points { + pointX := x + point[0] + pointY := y + point[1] + + if pointX < 0 || pointX >= i.Width || pointY < 0 || pointY >= i.Height { + continue + } + + r, g, b, a := i.RgbaImage.RGBAAt(pointX, pointY).RGBA() + red += r >> 8 + green += g >> 8 + blue += b >> 8 + alpha += a >> 8 + + } + + var avg uint32 + avg = 8 + + rgba := color.RGBA{R: uint8(red / avg), G: uint8(green / avg), B: uint8(blue / avg), A: uint8(alpha / avg)} + + i.RgbaImage.SetRGBA(x, y, rgba) +} + +// OpenPngImage 打开png图片 +func OpenPngImage(src string) image.Image { + ff, err := os.Open(src) + if err != nil { + log.Printf("打开 %s 图片失败: %v", src, err) + } + + img, err := png.Decode(ff) + + if err != nil { + log.Printf("png %s decode 失败: %v", src, err) + } + + return img +} + +// ImageToRGBA 图片转rgba +func ImageToRGBA(img image.Image) *image.RGBA { + // No conversion needed if image is an *image.RGBA. + if dst, ok := img.(*image.RGBA); ok { + return dst + } + + // Use the image/draw package to convert to *image.RGBA. + b := img.Bounds() + dst := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy())) + draw.Draw(dst, dst.Bounds(), img, b.Min, draw.Src) + return dst +} + +// CurrentAbPath 获取项目根目录 +func CurrentAbPath() (dir string) { + + // 如果是go run则返回temp目录 go build 则返回当前目录 + dir = getCurrentAbPathByExecutable() + + tempDir := getTmpDir() + + // 如果是临时目录执行 从Caller中获取 + if strings.Contains(dir, tempDir) || tempDir == "." { + dir = getCurrentAbPathByCaller() + } + + // 执行目录非util目录 + if !strings.HasSuffix(dir, "util") { + dir += "/util" + } + + return filepath.Dir(dir) +} + +// 获取当前执行文件绝对路径 +func getCurrentAbPathByExecutable() string { + exePath, err := os.Executable() + if err != nil { + log.Fatal(err) + } + res, _ := filepath.EvalSymlinks(filepath.Dir(exePath)) + return res +} + +// 获取当前执行文件绝对路径(go run) +func getCurrentAbPathByCaller() string { + var abPath string + _, filename, _, ok := runtime.Caller(0) + if ok { + abPath = path.Dir(filename) + } + return abPath +} + +// 获取系统临时目录,兼容go run +func getTmpDir() string { + dir := os.Getenv("TEMP") + if dir == "" { + dir = os.Getenv("TMP") + } + res, _ := filepath.EvalSymlinks(dir) + return res +} diff --git a/server/util/aj-captcha-go/util/random_util.go b/server/util/aj-captcha-go/util/random_util.go new file mode 100644 index 0000000..0fbb12a --- /dev/null +++ b/server/util/aj-captcha-go/util/random_util.go @@ -0,0 +1,14 @@ +package util + +import ( + "math/rand" + "time" +) + +func RandomInt(min, max int) int { + if min >= max || max == 0 { + return max + } + rand.Seed(time.Now().UnixNano()) + return rand.Intn(max-min) + min +} diff --git a/server/util/aj-captcha-go/util/redis_util.go b/server/util/aj-captcha-go/util/redis_util.go new file mode 100644 index 0000000..7506c3f --- /dev/null +++ b/server/util/aj-captcha-go/util/redis_util.go @@ -0,0 +1,97 @@ +package util + +import ( + "context" + "fmt" + "github.com/go-redis/redis/v8" + "strconv" + "time" +) + +type RedisUtil struct { + Rdb redis.UniversalClient +} + +// InitConfigRedis 初始化自定义配置redis客户端(可单机, 可集群) +func (l *RedisUtil) InitConfigRedis(rdsAddr []string, dbUserName, dbPassword string, enableCluster bool, db int) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if enableCluster { + l.Rdb = redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: rdsAddr, + Username: dbUserName, + Password: dbPassword, + PoolSize: 100, + }) + _, err := l.Rdb.Ping(ctx).Result() + if err != nil { + panic(err.Error()) + } + } else { + l.Rdb = redis.NewClient(&redis.Options{ + Addr: rdsAddr[0], + Username: dbUserName, + Password: dbPassword, // no password set + DB: db, // use select DB + PoolSize: 100, // 连接池大小 + }) + _, err := l.Rdb.Ping(ctx).Result() + if err != nil { + panic(err.Error()) + } + } +} + +func NewConfigRedisUtil(rdsAddr []string, dbUserName, dbPassword string, enableCluster bool, db int) *RedisUtil { + redisUtil := &RedisUtil{} + redisUtil.InitConfigRedis(rdsAddr, dbUserName, dbPassword, enableCluster, db) + return redisUtil +} + +func (l *RedisUtil) Exists(key string) bool { + timeVal := l.Rdb.Get(context.Background(), key+"_HoldTime").Val() + cacheHoldTime, err := strconv.ParseInt(timeVal, 10, 64) + + if err != nil { + return false + } + + if cacheHoldTime == 0 { + return true + } + + if cacheHoldTime < time.Now().Unix() { + l.Delete(key) + return false + } + return true +} + +func (l *RedisUtil) Get(key string) string { + val := l.Rdb.Get(context.Background(), key).Val() + return val +} + +func (l *RedisUtil) Set(key string, val string, expiresInSeconds int) { + //设置阈值,达到即clear缓存 + rdsResult := l.Rdb.Set(context.Background(), key, val, time.Duration(expiresInSeconds)*time.Second) + fmt.Println("rdsResult: ", rdsResult.String(), "rdsErr: ", rdsResult.Err()) + if expiresInSeconds > 0 { + // 缓存失效时间 + nowTime := time.Now().Unix() + int64(expiresInSeconds) + l.Rdb.Set(context.Background(), key+"_HoldTime", strconv.FormatInt(nowTime, 10), time.Duration(expiresInSeconds)*time.Second) + } else { + l.Rdb.Set(context.Background(), key+"_HoldTime", strconv.FormatInt(0, 10), time.Duration(expiresInSeconds)*time.Second) + } +} + +func (l *RedisUtil) Delete(key string) { + l.Rdb.Del(context.Background(), key) + l.Rdb.Del(context.Background(), key+"_HoldTime") +} + +func (l *RedisUtil) Clear() { + //for key, _ := range l.Data { + // l.Delete(key) + //} +} diff --git a/server/util/aj-captcha-go/util/uuid.go b/server/util/aj-captcha-go/util/uuid.go new file mode 100644 index 0000000..26c68cf --- /dev/null +++ b/server/util/aj-captcha-go/util/uuid.go @@ -0,0 +1,34 @@ +package util + +import ( + "bytes" + "crypto/rand" + "fmt" + "io" + rand2 "math/rand" + "time" +) + +// GetUuid 获取UUID +func GetUuid() string { + b := make([]byte, 16) + io.ReadFull(rand.Reader, b) + b[6] = (b[6] & 0x0f) | 0x40 + b[8] = (b[8] & 0x3f) | 0x80 + return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) +} + +func RandString(codeLen int) string { + // 1. 定义原始字符串 + rawStr := "jkwangagDGFHGSERKILMJHSNOPQR546413890_" + // 2. 定义一个buf,并且将buf交给bytes往buf中写数据 + buf := make([]byte, 0, codeLen) + b := bytes.NewBuffer(buf) + // 随机从中获取 + rand2.Seed(time.Now().UnixNano()) + for rawStrLen := len(rawStr); codeLen > 0; codeLen-- { + randNum := rand2.Intn(rawStrLen) + b.WriteByte(rawStr[randNum]) + } + return b.String() +}