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 b067997..7824fd8 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -53,7 +53,8 @@ tools: enabled: true DingTalkBot: enabled: true - + 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/biz/session.go b/internal/biz/session.go index e1dd17e..19b3e8a 100644 --- a/internal/biz/session.go +++ b/internal/biz/session.go @@ -59,10 +59,11 @@ func (s *SessionBiz) SessionInit(ctx context.Context, req *entitys.SessionInitRe } else if !has { // 不存在,创建一个 session = model.AiSession{ - SysID: sysConfig.SysID, - SessionID: utils.UUID(), - UserID: req.UserId, - UserName: req.UserName, + SysID: sysConfig.SysID, + SessionID: utils.UUID(), + UserID: req.UserId, + UserName: req.UserName, + DingUserId: req.DingUserId, } err = s.sessionRepo.Create(&session) if err != nil { diff --git a/internal/config/config.go b/internal/config/config.go index f0c80da..d39d80a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -93,9 +93,10 @@ type ToolsConfig struct { // ToolConfig 单个工具配置 type ToolConfig struct { - Enabled bool `mapstructure:"enabled"` - BaseURL string `mapstructure:"base_url"` - APIKey string `mapstructure:"api_key"` + Enabled bool `mapstructure:"enabled"` + BaseURL string `mapstructure:"base_url"` + APIKey string `mapstructure:"api_key"` + APISecret string `mapstructure:"api_secret"` //附加地址 AddURL string `mapstructure:"add_url"` } diff --git a/internal/data/model/ai_session.gen.go b/internal/data/model/ai_session.gen.go index d872adb..7a2ce0a 100644 --- a/internal/data/model/ai_session.gen.go +++ b/internal/data/model/ai_session.gen.go @@ -20,8 +20,9 @@ type AiSession struct { UpdateAt *time.Time `gorm:"column:update_at;default:CURRENT_TIMESTAMP" json:"update_at"` Status int32 `gorm:"column:status;not null" json:"status"` DeleteAt *time.Time `gorm:"column:delete_at" json:"delete_at"` - UserID string `gorm:"column:user_id;comment:用户id" json:"user_id"` // 用户id - UserName string `gorm:"column:user_name;comment:用户名称" json:"user_name"` // 用户id + UserID string `gorm:"column:user_id;comment:用户id" json:"user_id"` // 用户id + UserName string `gorm:"column:user_name;comment:用户名称" json:"user_name"` // 用户名称 + DingUserId string `gorm:"column:ding_user_id;comment:钉钉用户id" json:"ding_user_id"` // 钉钉用户id } // TableName AiSession's table name diff --git a/internal/entitys/session.go b/internal/entitys/session.go index 74a517a..e02ddb9 100644 --- a/internal/entitys/session.go +++ b/internal/entitys/session.go @@ -1,9 +1,10 @@ package entitys type SessionInitRequest struct { - SysId string `json:"sys_id"` - UserId string `json:"user_id"` - UserName string `json:"user_name"` + SysId string `json:"sys_id"` + UserId string `json:"user_id"` + UserName string `json:"user_name"` + DingUserId string `json:"ding_user_id"` } type SessionInitResponse struct { diff --git a/internal/pkg/dingtalk/client.go b/internal/pkg/dingtalk/client.go new file mode 100644 index 0000000..f1c5b12 --- /dev/null +++ b/internal/pkg/dingtalk/client.go @@ -0,0 +1,105 @@ +package dingtalk + +import ( + "ai_scheduler/internal/config" + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/url" + "os" + + "github.com/faabiosr/cachego/file" + "github.com/fastwego/dingding" +) + +type Client struct { + cfg *config.Config + sdkClient *dingding.Client +} + +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"` + Name string `json:"name"` + UnionID string `json:"unionid"` +} + +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) + } + 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) + res, err := c.do(ctx, http.MethodPost, "/topapi/v2/user/get", b) + if err != nil { + return nil, err + } + var resp struct { + Code int `json:"errcode"` + Msg string `json:"errmsg"` + Result UserDetail `json:"result"` + } + if err := json.Unmarshal(res, &resp); err != nil { + return nil, err + } + if resp.Code != 0 { + return nil, errors.New(resp.Msg) + } + return &resp.Result, nil +} + +func (c *Client) QueryUserDetailsByMobile(ctx context.Context, mobile string) (*UserDetail, error) { + body := struct { + Mobile string `json:"mobile"` + }{Mobile: mobile} + b, _ := json.Marshal(body) + res, err := c.do(ctx, http.MethodPost, "/topapi/v2/user/getbymobile", b) + if err != nil { + return nil, err + } + var resp struct { + Code int `json:"errcode"` + Msg string `json:"errmsg"` + Result UserDetail `json:"result"` + } + if err := json.Unmarshal(res, &resp); err != nil { + return nil, err + } + if resp.Code != 0 { + return nil, errors.New(resp.Msg) + } + return &resp.Result, nil +} diff --git a/internal/pkg/provider_set.go b/internal/pkg/provider_set.go index e20b9c2..e7674b7 100644 --- a/internal/pkg/provider_set.go +++ b/internal/pkg/provider_set.go @@ -1,6 +1,7 @@ package pkg import ( + "ai_scheduler/internal/pkg/dingtalk" "ai_scheduler/internal/pkg/utils_langchain" "ai_scheduler/internal/pkg/utils_ollama" @@ -13,4 +14,5 @@ var ProviderSetClient = wire.NewSet( utils_langchain.NewUtilLangChain, utils_ollama.NewClient, NewSafeChannelPool, + dingtalk.NewDingTalkClient, ) 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/server/http.go b/internal/server/http.go index d4df3a1..4cdc393 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -11,22 +11,24 @@ import ( ) type HTTPServer struct { - app *fiber.App - service *services.ChatService - session *services.SessionService - gateway *gateway.Gateway + app *fiber.App + service *services.ChatService + session *services.SessionService + gateway *gateway.Gateway + callback *services.CallbackService } func NewHTTPServer( - service *services.ChatService, - session *services.SessionService, - task *services.TaskService, - gateway *gateway.Gateway, + service *services.ChatService, + session *services.SessionService, + task *services.TaskService, + gateway *gateway.Gateway, + callback *services.CallbackService, ) *fiber.App { - //构建 server - app := initRoute() - router.SetupRoutes(app, service, session, task, gateway) - return app + //构建 server + app := initRoute() + router.SetupRoutes(app, service, session, task, gateway, callback) + return app } func initRoute() *fiber.App { diff --git a/internal/server/router/router.go b/internal/server/router/router.go index 04fbc7c..a2bbd81 100644 --- a/internal/server/router/router.go +++ b/internal/server/router/router.go @@ -22,7 +22,7 @@ type RouterServer struct { } // SetupRoutes 设置路由 -func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionService *services.SessionService, task *services.TaskService, gateway *gateway.Gateway) { +func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionService *services.SessionService, task *services.TaskService, gateway *gateway.Gateway, callbackService *services.CallbackService) { app.Use(func(c *fiber.Ctx) error { // 设置 CORS 头 c.Set("Access-Control-Allow-Origin", "*") @@ -51,6 +51,8 @@ func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionServi r.Post("/session/init", sessionService.SessionInit) // 会话初始化,不存在则创建,存在则返回会话ID和默认条数会话历史 r.Post("/session/list", sessionService.SessionList) r.Post("/sys/tasks", task.Tasks) + // 回调 + r.Post("/callback", callbackService.Callback) //广播 r.Get("/broadcast", func(ctx *fiber.Ctx) error { action := ctx.Query("action") diff --git a/internal/services/callback.go b/internal/services/callback.go new file mode 100644 index 0000000..f1a9447 --- /dev/null +++ b/internal/services/callback.go @@ -0,0 +1,178 @@ +package services + +import ( + "ai_scheduler/internal/config" + 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" + "strings" + "time" + + "github.com/gofiber/fiber/v2" +) + +// CallbackService 统一回调入口 +type CallbackService struct { + cfg *config.Config + gateway *gateway.Gateway + dingtalkClient *dingtalk.Client + botTool *tools_bot.BotTool +} + +func NewCallbackService(cfg *config.Config, gateway *gateway.Gateway, dingtalkClient *dingtalk.Client, botTool *tools_bot.BotTool) *CallbackService { + return &CallbackService{ + cfg: cfg, + gateway: gateway, + dingtalkClient: dingtalkClient, + botTool: botTool, + } +} + +// Envelope 回调统一请求体 +type Envelope struct { + Action string `json:"action"` + TaskID string `json:"task_id"` + Data json.RawMessage `json:"data"` +} + +// bug_optimization_submit_done 工单完成回调 +const ActionBugOptimizationSubmitDone = "bug_optimization_submit_done" + +// BugOptimizationSubmitDoneData 工单完成回调数据 +type BugOptimizationSubmitDoneData struct { + Receivers []string `json:"receivers"` + DetailPage string `json:"detail_page"` + Msg string `json:"msg"` +} + +// Callback 统一回调处理 +// 头部:X-Source-Key / X-Timestamp +func (s *CallbackService) Callback(c *fiber.Ctx) error { + // 读取头 + sourceKey := strings.TrimSpace(c.Get("X-Source-Key")) + ts := strings.TrimSpace(c.Get("X-Timestamp")) + + // 时间窗口(如果提供了 ts 则校验,否则跳过),窗口 5 分钟 + if ts != "" && !validateTimestamp(ts, 5*time.Minute) { + return errorcode.AuthNotFound + } + + // 解析 Envelope + var env Envelope + if err := json.Unmarshal(c.Body(), &env); err != nil { + return errorcode.ParamErr("invalid json: %v", err) + } + if env.Action == "" || env.TaskID == "" { + return errorcode.ParamErr("missing action/task_id") + } + if env.Data == nil { + return errorcode.ParamErr("missing data") + } + + switch sourceKey { + case "dingtalk": + return s.handleDingTalkCallback(c, env) + default: + return errorcode.AuthNotFound + } +} + +func validateTimestamp(ts string, window time.Duration) bool { + // 期望毫秒时间戳或秒级,简单容错 + // 尝试解析为整数 + var n int64 + for _, base := range []int64{1, 1000} { // 秒或毫秒 + if v, ok := parseInt64(ts); ok { + n = v + // 归一为毫秒 + if base == 1 && len(ts) <= 10 { + n = n * 1000 + } + now := time.Now().UnixMilli() + diff := now - n + if diff < 0 { + diff = -diff + } + if diff <= window.Milliseconds() { + return true + } + } + } + return false +} + +func parseInt64(s string) (int64, bool) { + var n int64 + for _, ch := range s { + if ch < '0' || ch > '9' { + return 0, false + } + n = n*10 + int64(ch-'0') + } + return n, true +} + +func (s *CallbackService) handleDingTalkCallback(c *fiber.Ctx, env Envelope) error { + switch env.Action { + // bug/优化完成回调 + case ActionBugOptimizationSubmitDone: + // 获取 session_id + sessionID, ok := s.botTool.GetSessionByTaskID(env.TaskID) + if !ok { + return errorcode.ParamErr("missing session_id for task_id: %s", env.TaskID) + } + + var data BugOptimizationSubmitDoneData + if err := json.Unmarshal(env.Data, &data); err != nil { + return errorcode.ParamErr("invalid data type: %v", err) + } + + if len(data.Receivers) == 0 { + return errorcode.ParamErr("empty receivers") + } + // 构建接收者 + receivers := s.getDingtalkReceivers(c.Context(), data.Receivers) + + // 构建跳转链接 + var detailPage string + if data.DetailPage != "" { + detailPage = util.BuildJumpLink(data.DetailPage, "去查看") + } + + msg := 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/services/provider_set.go b/internal/services/provider_set.go index 70987fa..0e1284b 100644 --- a/internal/services/provider_set.go +++ b/internal/services/provider_set.go @@ -6,4 +6,4 @@ import ( "github.com/google/wire" ) -var ProviderSetServices = wire.NewSet(NewChatService, NewSessionService, gateway.NewGateway, NewTaskService) +var ProviderSetServices = wire.NewSet(NewChatService, NewSessionService, gateway.NewGateway, NewTaskService, NewCallbackService) diff --git a/internal/tools_bot/dtalk_bot.go b/internal/tools_bot/dtalk_bot.go index 99270b0..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 执行直连天下订单详情查询 @@ -49,13 +51,24 @@ func (w *BotTool) Execute(ctx context.Context, toolName string, requireData *ent return } +const ( + // 工单QA + BotBugOptimizationSubmitQA = "温子新" + BotBugOptimizationSubmitPM = "贺泽琨" +) + +// 现存问题: +// 1. 回调时 session 直接传入不安全 todo +// 2. 创建人无法指定[钉钉用户],影响后续状态变化时通知 +// 3. 回调接口,[接收人]、[文档地址不能]动态配置 +// 4. 测试环境与线上环境,使用的不是同一个钉钉主体 func (w *BotTool) BugOptimizationSubmit(ctx context.Context, requireData *entitys.RequireData) (err error) { // 获取用户信息 cond := builder.NewCond() 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) @@ -66,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{ @@ -85,14 +98,37 @@ 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"] { - entitys.ResLoading(requireData.Ch, requireData.Match.Index, "问题信息记录中...") + // 记录 task_id 到 session_id 的映射 + w.SetTaskMapping(body.TaskId, requireData.Session) + + entitys.ResLoading(requireData.Ch, requireData.Match.Index, "问题内容记录中...") return } - entitys.ResJson(requireData.Ch, requireData.Match.Index, "bug问题请咨询 @温子新 ,优化建议请咨询 @贺泽琨 。") + 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) +}