diff --git a/.gitignore b/.gitignore index 6f6550a..188273e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .vscode .env +.trae/ docs cmd/server/wire_gen.go __debug* \ No newline at end of file diff --git a/config/config.yaml b/config/config.yaml index 06ff171..74d865f 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -53,8 +53,8 @@ tools: enabled: true DingTalkBot: enabled: true - api_key: "dingwxaswioywe5dyw7u" - api_secret: "WZtr20zOMlQBEIkOdh2-K1no_spVWYNzD8LJZm1fPGuSSQQT1lu0iqTIqnJhCG0Q" + api_key: "dingsbbntrkeiyazcfdg" + api_secret: "ObqxwyR20r9rVNhju0sCPQyQA98_FZSc32W4vgxnGFH_b02HZr1BPCJsOAF816nu" default_prompt: img_recognize: diff --git a/go.mod b/go.mod index 2c435df..a38028a 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,12 @@ toolchain go1.24.7 require ( gitea.cdlsxd.cn/self-tools/l_request v1.0.8 github.com/emirpasic/gods v1.18.1 + github.com/faabiosr/cachego v0.26.0 + github.com/fastwego/dingding v1.0.0-beta.4 github.com/go-kratos/kratos/v2 v2.9.1 github.com/gofiber/fiber/v2 v2.52.9 github.com/gofiber/websocket/v2 v2.2.1 + github.com/google/uuid v1.6.0 github.com/google/wire v0.7.0 github.com/ollama/ollama v0.12.7 github.com/redis/go-redis/v9 v9.16.0 @@ -32,7 +35,6 @@ require ( github.com/fasthttp/websocket v1.5.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect diff --git a/go.sum b/go.sum index 2eff569..f0d3ba1 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,7 @@ 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/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -76,12 +77,20 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/faabiosr/cachego v0.15.0/go.mod h1:L2EomlU3/rUWjzFavY9Fwm8B4zZmX2X6u8kTMkETrwI= +github.com/faabiosr/cachego v0.26.0 h1:EDDv2y9T0XJ4Cx3tUhbKSUayGWxCGkkZUivNLceHRWY= +github.com/faabiosr/cachego v0.26.0/go.mod h1:p54WXVzeB1CctH1ix/rjqv1EotNzD0Xoxk2IsR1PQX8= github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek= github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs= +github.com/fastwego/dingding v1.0.0-beta.4 h1:lhO7OyCIvhFQb/N8OAwi/KDqtq94tb06FfnDGfv5PKM= +github.com/fastwego/dingding v1.0.0-beta.4/go.mod h1:EphZlfmXhp/2PSkQt1sy9Q1P2Co/5ioFZUXPyyy4wY4= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -157,6 +166,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 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/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -185,10 +195,18 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/ollama/ollama v0.12.7 h1:dxokli1UyO/a0Aun5sE4+0Gg+A9oMUAPiFQhxrXOIXA= github.com/ollama/ollama v0.12.7/go.mod h1:9+1//yWPsDE2u+l1a5mpaKrYw4VdnSsRU3ioq5BvMms= +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.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +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/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -212,6 +230,7 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= @@ -250,6 +269,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -306,6 +326,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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= @@ -326,6 +347,7 @@ 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= @@ -360,6 +382,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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= @@ -368,7 +391,10 @@ 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-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= @@ -382,6 +408,7 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -560,14 +587,22 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= 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-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/mgo.v2 v2.0.0-20160818020120-3f83fa500528/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/pkg/dingtalk/client.go b/internal/pkg/dingtalk/client.go index bc3ebd0..f1c5b12 100644 --- a/internal/pkg/dingtalk/client.go +++ b/internal/pkg/dingtalk/client.go @@ -9,15 +9,32 @@ import ( "io" "net/http" "net/url" + "os" + + "github.com/faabiosr/cachego/file" + "github.com/fastwego/dingding" ) -// Client 使用官方接口获取 AccessToken,并通过 HTTP 直接调用 TopAPI type Client struct { - cfg *config.Config + cfg *config.Config + sdkClient *dingding.Client } -// NewDingTalkClient 基于配置创建钉钉客户端(无 fastwego 依赖) -func NewDingTalkClient(cfg *config.Config) *Client { return &Client{cfg: cfg} } +func NewDingTalkClient(cfg *config.Config) *Client { + atm := &dingding.DefaultAccessTokenManager{ + Id: cfg.Tools.DingTalkBot.APIKey, + Name: "access_token", + GetRefreshRequestFunc: func() *http.Request { + params := url.Values{} + params.Add("appkey", cfg.Tools.DingTalkBot.APIKey) + params.Add("appsecret", cfg.Tools.DingTalkBot.APISecret) + req, _ := http.NewRequest(http.MethodGet, dingding.ServerUrl+"/gettoken?"+params.Encode(), nil) + return req + }, + Cache: file.New(os.TempDir()), + } + return &Client{cfg: cfg, sdkClient: dingding.NewClient(atm)} +} type UserDetail struct { UserID string `json:"userid"` @@ -25,75 +42,31 @@ type UserDetail struct { UnionID string `json:"unionid"` } -// getAccessToken 通过官方接口获取应用 AccessToken(简单实现,每次请求刷新,不做缓存) -func (c *Client) getAccessToken(ctx context.Context) (string, error) { - // 调用旧版 OAPI 获取 access_token,供 /topapi 使用 - appKey := c.cfg.Tools.DingTalkBot.APIKey - appSecret := c.cfg.Tools.DingTalkBot.APISecret - params := url.Values{} - params.Add("appkey", appKey) - params.Add("appsecret", appSecret) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://oapi.dingtalk.com/gettoken?"+params.Encode(), nil) - if err != nil { - return "", err +func (c *Client) do(ctx context.Context, method, path string, body []byte) ([]byte, error) { + var r io.Reader + if body != nil { + r = bytes.NewReader(body) } - resp, err := http.DefaultClient.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - var r struct { - ErrCode int `json:"errcode"` - ErrMsg string `json:"errmsg"` - AccessToken string `json:"access_token"` - ExpiresIn int `json:"expires_in"` - } - if err := json.Unmarshal(b, &r); err != nil { - return "", err - } - if r.ErrCode != 0 || r.AccessToken == "" { - if r.ErrMsg == "" { - r.ErrMsg = "gettoken failed" - } - return "", errors.New(r.ErrMsg) - } - return r.AccessToken, nil -} - -// QueryUserDetails 按 userId 查询用户详情 -func (c *Client) QueryUserDetails(ctx context.Context, userId string) (*UserDetail, error) { - accessToken, err := c.getAccessToken(ctx) + req, err := http.NewRequestWithContext(ctx, method, path, r) if err != nil { return nil, err } + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + return c.sdkClient.Do(req) +} + +func (c *Client) QueryUserDetails(ctx context.Context, userId string) (*UserDetail, error) { body := struct { UserId string `json:"userid"` Language string `json:"language,omitempty"` }{UserId: userId, Language: "zh_CN"} b, _ := json.Marshal(body) - // 直接调用 TopAPI(oapi.dingtalk.com),通过 query 参数携带 access_token - // 官方 SDK 无 topapi 封装,此处保留原接口以保证行为一致 - params := url.Values{} - params.Add("access_token", accessToken) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://oapi.dingtalk.com/topapi/v2/user/get?"+params.Encode(), bytes.NewReader(b)) + res, err := c.do(ctx, http.MethodPost, "/topapi/v2/user/get", b) if err != nil { return nil, err } - req.Header.Set("Content-Type", "application/json") - httpResp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - defer httpResp.Body.Close() - res, err := io.ReadAll(httpResp.Body) - if err != nil { - return nil, err - } - var resp struct { Code int `json:"errcode"` Msg string `json:"errmsg"` @@ -105,38 +78,18 @@ func (c *Client) QueryUserDetails(ctx context.Context, userId string) (*UserDeta if resp.Code != 0 { return nil, errors.New(resp.Msg) } - return &resp.Result, nil } -// QueryUserDetailsByMobile 按手机号查询用户详情 func (c *Client) QueryUserDetailsByMobile(ctx context.Context, mobile string) (*UserDetail, error) { - accessToken, err := c.getAccessToken(ctx) - if err != nil { - return nil, err - } body := struct { Mobile string `json:"mobile"` }{Mobile: mobile} b, _ := json.Marshal(body) - // 直接调用 TopAPI(oapi.dingtalk.com),通过 query 参数携带 access_token - params := url.Values{} - params.Add("access_token", accessToken) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://oapi.dingtalk.com/topapi/v2/user/getbymobile?"+params.Encode(), bytes.NewReader(b)) + res, err := c.do(ctx, http.MethodPost, "/topapi/v2/user/getbymobile", b) if err != nil { return nil, err } - req.Header.Set("Content-Type", "application/json") - httpResp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - defer httpResp.Body.Close() - res, err := io.ReadAll(httpResp.Body) - if err != nil { - return nil, err - } - var resp struct { Code int `json:"errcode"` Msg string `json:"errmsg"` @@ -148,6 +101,5 @@ func (c *Client) QueryUserDetailsByMobile(ctx context.Context, mobile string) (* if resp.Code != 0 { return nil, errors.New(resp.Msg) } - return &resp.Result, nil } diff --git a/internal/pkg/util/string.go b/internal/pkg/util/string.go new file mode 100644 index 0000000..5ccac7d --- /dev/null +++ b/internal/pkg/util/string.go @@ -0,0 +1,13 @@ +package util + +import "strings" + +// 占位符替换 xxx{placeholder}xxx +func ReplacePlaceholder(str string, placeholder string, value string) string { + return strings.ReplaceAll(str, "{"+placeholder+"}", value) +} + +// 构建跳转a标签,新tab打开 +func BuildJumpLink(url string, text string) string { + return "" + text + "" +} diff --git a/internal/services/callback.go b/internal/services/callback.go index 6450ebd..3a69526 100644 --- a/internal/services/callback.go +++ b/internal/services/callback.go @@ -5,8 +5,10 @@ import ( errorcode "ai_scheduler/internal/data/error" "ai_scheduler/internal/gateway" "ai_scheduler/internal/pkg/dingtalk" + "ai_scheduler/internal/pkg/util" + "ai_scheduler/internal/tools_bot" + "context" "encoding/json" - "fmt" "strings" "time" @@ -17,16 +19,16 @@ import ( type CallbackService struct { cfg *config.Config gateway *gateway.Gateway - taskMap map[string]string // task_id -> session_id dingtalkClient *dingtalk.Client + botTool *tools_bot.BotTool } -func NewCallbackService(cfg *config.Config, gateway *gateway.Gateway, dingtalkClient *dingtalk.Client) *CallbackService { +func NewCallbackService(cfg *config.Config, gateway *gateway.Gateway, dingtalkClient *dingtalk.Client, botTool *tools_bot.BotTool) *CallbackService { return &CallbackService{ cfg: cfg, gateway: gateway, - taskMap: map[string]string{}, dingtalkClient: dingtalkClient, + botTool: botTool, } } @@ -37,22 +39,6 @@ type Envelope struct { Data map[string]string `json:"data"` } -// SetTaskMapping 设置 task_id 到 session_id 的映射(内存版)。 -// 注意:生产环境建议使用 Redis/DB + TTL,确保幂等与过期清理。 -func (s *CallbackService) SetTaskMapping(taskID, sessionID string) { - if taskID == "" || sessionID == "" { - return - } - s.taskMap[taskID] = sessionID -} - -// GetSessionByTaskID 读取映射 -func (s *CallbackService) GetSessionByTaskID(taskID string) (string, bool) { - return "bf0c6873-1df2-4d46-aa3c-8f7456e7efca", true - v, ok := s.taskMap[taskID] - return v, ok -} - // Callback 统一回调处理 // 头部:X-Source-Key / X-Timestamp func (s *CallbackService) Callback(c *fiber.Ctx) error { @@ -125,40 +111,62 @@ func (s *CallbackService) handleDingTalkCallback(c *fiber.Ctx, env Envelope) err // bug/优化完成回调 case "bug_optimization_submit_done": // 获取 session_id - sessionID, ok := s.GetSessionByTaskID(env.TaskID) + sessionID, ok := s.botTool.GetSessionByTaskID(env.TaskID) if !ok { return errorcode.ParamErr("missing session_id for task_id: %s", env.TaskID) } - // 获取接收者姓名 - receivers := env.Data["receivers"] - if receivers == "" { + // 获取接收者 + receiverJson := env.Data["receivers"] + if receiverJson == "" { return errorcode.ParamErr("missing receivers") } var receiverIds []string - if err := json.Unmarshal([]byte(receivers), &receiverIds); err != nil { + if err := json.Unmarshal([]byte(receiverJson), &receiverIds); err != nil { return errorcode.ParamErr("invalid receivers: %v", err) } if len(receiverIds) == 0 { return errorcode.ParamErr("empty receivers") } + // 构建接收者 + receivers := s.getDingtalkReceivers(c.Context(), receiverIds) - aaa, _ := s.dingtalkClient.QueryUserDetailsByMobile(c.Context(), "13126622913") - - userDetails, err := s.dingtalkClient.QueryUserDetails(c.Context(), aaa.UserID) - // userDetails, err := s.dingtalkClient.QueryUserDetails(c.Context(), receiverIds[0]) - if err != nil { - return errorcode.ParamErr("query user details failed: %v", err) - } - if userDetails == nil { - return errorcode.ParamErr("user details is nil") + // 构建跳转链接 + detailPage := env.Data["detail_page"] + if detailPage != "" { + detailPage = util.BuildJumpLink(detailPage, "去查看") } - msg := fmt.Sprintf(env.Data["msg"], userDetails.Name) + msg := env.Data["msg"] + msg = util.ReplacePlaceholder(msg, "receivers", receivers) + msg = util.ReplacePlaceholder(msg, "detail_page", detailPage) s.gateway.SendToUid(sessionID, []byte(msg)) + + // 删除映射 + s.botTool.DelTaskMapping(env.TaskID) + return c.JSON(fiber.Map{"code": 0, "message": "ok"}) default: return errorcode.ParamErr("unknown action: %s", env.Action) } } + +// getDingtalkReceivers 解析接收者字符串为 DingTalk 用户 ID 列表 +func (s *CallbackService) getDingtalkReceivers(ctx context.Context, receiverIds []string) string { + var receiverNames []string + for _, receiverId := range receiverIds { + userDetails, err := s.dingtalkClient.QueryUserDetails(ctx, receiverId) + if err != nil { + return "" + } + if userDetails == nil { + return "" + } + receiverNames = append(receiverNames, "@"+userDetails.Name) + } + + receivers := strings.Join(receiverNames, " ") + + return receivers +} diff --git a/internal/tools_bot/dtalk_bot.go b/internal/tools_bot/dtalk_bot.go index 80755c8..9b187fd 100644 --- a/internal/tools_bot/dtalk_bot.go +++ b/internal/tools_bot/dtalk_bot.go @@ -14,6 +14,7 @@ import ( "fmt" "github.com/gofiber/fiber/v2/log" + "github.com/google/uuid" "xorm.io/builder" ) @@ -21,6 +22,7 @@ type BotTool struct { config *config.Config llm *utils_ollama.Client sessionImpl *impl.SessionImpl + taskMap map[string]string // task_id -> session_id } // NewBotTool 创建直连天下订单详情工具 @@ -34,7 +36,7 @@ type BugOptimizationSubmitForm struct { Text string `json:"text"` // 工单描述 Img string `json:"img"` // 工单截图 Creator string `json:"creator"` // 工单创建人 - Session string `json:"session"` // 会话ID + TaskId string `json:"task_id"` // 当初任务ID } // Execute 执行直连天下订单详情查询 @@ -66,7 +68,7 @@ func (w *BotTool) BugOptimizationSubmit(ctx context.Context, requireData *entity cond = cond.And(builder.Eq{"session_id": requireData.Session}) sessionInfo, err := w.sessionImpl.GetOneBySearch(&cond) if err != nil { - err = errors.SysErr("获取历史记录失败:%v", err.Error()) + err = errors.SysErr("获取会话信息失败:%v", err.Error()) return } userName := sessionInfo["user_name"].(string) @@ -77,7 +79,7 @@ func (w *BotTool) BugOptimizationSubmit(ctx context.Context, requireData *entity Text: requireData.Req.Text, Img: requireData.Req.Img, Creator: userName, - Session: requireData.Session, + TaskId: uuid.New().String(), } request := l_request.Request{ @@ -96,10 +98,13 @@ func (w *BotTool) BugOptimizationSubmit(ctx context.Context, requireData *entity data := make(map[string]bool) if err = json.Unmarshal(res.Content, &data); err != nil { - return fmt.Errorf("解析工单回调失败:%w", err) + return fmt.Errorf("解析工单响应失败:%w", err) } if data["success"] { + // 记录 task_id 到 session_id 的映射 + w.SetTaskMapping(body.TaskId, requireData.Session) + entitys.ResLoading(requireData.Ch, requireData.Match.Index, "问题内容记录中...") return } @@ -107,3 +112,23 @@ func (w *BotTool) BugOptimizationSubmit(ctx context.Context, requireData *entity entitys.ResJson(requireData.Ch, requireData.Match.Index, fmt.Sprintf("bug问题请咨询 @%s ,优化建议请咨询 @%s 。", BotBugOptimizationSubmitQA, BotBugOptimizationSubmitPM)) return } + +// SetTaskMapping 设置 task_id 到 session_id 的映射(内存版)。 +// 后续考虑使用 Redis,确保幂等与过期清理。 +func (w *BotTool) SetTaskMapping(taskID, sessionID string) { + if taskID == "" || sessionID == "" { + return + } + w.taskMap[taskID] = sessionID +} + +// GetSessionByTaskID 读取映射 +func (w *BotTool) GetSessionByTaskID(taskID string) (string, bool) { + v, ok := w.taskMap[taskID] + return v, ok +} + +// DelTaskMapping 删除 task_id 到 session_id 的映射(内存版)。 +func (w *BotTool) DelTaskMapping(taskID string) { + delete(w.taskMap, taskID) +}