1. 调整dingtalk客户端 2.钉钉配置替换为蓝色兄弟主体 3.增加一些公共方法 4.增加回调http方法,支持钉钉用户获取,占位符替换

This commit is contained in:
fuzhongyun 2025-11-14 10:28:35 +08:00
parent 47ceb06a13
commit 3fb3ce649f
8 changed files with 161 additions and 125 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
.vscode
.env
.trae/
docs
cmd/server/wire_gen.go
__debug*

View File

@ -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:

4
go.mod
View File

@ -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

35
go.sum
View File

@ -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=

View File

@ -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)
// 直接调用 TopAPIoapi.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)
// 直接调用 TopAPIoapi.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
}

View File

@ -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 "<a href=\"" + url + "\" target=\"_blank\">" + text + "</a>"
}

View File

@ -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
}

View File

@ -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)
}