From 0d8ef7056ca6342abc9ffbe3a41a4dfadfb9a87c Mon Sep 17 00:00:00 2001 From: renzhiyuan <465386466@qq.com> Date: Mon, 29 Dec 2025 17:11:42 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0bbxt=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E5=8C=85=E5=92=8C=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 19 +-- go.sum | 39 ++++-- internal/pkg/func.go | 193 ++++++++++++++++++++++++++++ internal/tools/bbxt/api.go | 210 +++++++++++++++++++++++++++++++ internal/tools/bbxt/bbxt.go | 133 ++++++++++++++++++++ internal/tools/bbxt/bbxt_test.go | 14 +++ tmpl/excel_temp/kshj_total.xlsx | Bin 0 -> 10313 bytes 7 files changed, 589 insertions(+), 19 deletions(-) create mode 100644 internal/tools/bbxt/api.go create mode 100644 internal/tools/bbxt/bbxt.go create mode 100644 internal/tools/bbxt/bbxt_test.go create mode 100644 tmpl/excel_temp/kshj_total.xlsx diff --git a/go.mod b/go.mod index 76bed20..694ee17 100644 --- a/go.mod +++ b/go.mod @@ -25,11 +25,13 @@ require ( github.com/gofiber/websocket/v2 v2.2.1 github.com/google/uuid v1.6.0 github.com/google/wire v0.7.0 + github.com/json-iterator/go v1.1.12 github.com/ollama/ollama v0.12.7 github.com/redis/go-redis/v9 v9.16.0 github.com/spf13/viper v1.17.0 github.com/tmc/langchaingo v0.1.13 - golang.org/x/sync v0.15.0 + github.com/xuri/excelize/v2 v2.10.0 + golang.org/x/sync v0.17.0 google.golang.org/grpc v1.64.0 gorm.io/driver/mysql v1.6.0 gorm.io/gorm v1.31.0 @@ -69,7 +71,6 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -86,6 +87,8 @@ require ( github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkoukk/tiktoken-go v0.1.6 // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.4 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -96,23 +99,25 @@ require ( github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/testify v1.11.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tiendc/go-deepcopy v1.7.1 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + github.com/xuri/efp v0.0.1 // indirect + github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect github.com/yargevad/filepathx v1.0.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.11.0 // indirect - golang.org/x/crypto v0.39.0 // indirect + golang.org/x/crypto v0.43.0 // indirect golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index a6e9a9c..b753e6a 100644 --- a/go.sum +++ b/go.sum @@ -368,6 +368,11 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4= github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00= +github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -423,6 +428,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4= +github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= @@ -440,6 +447,12 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/ github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8= +github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4= +github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU= +github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE= +github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -479,8 +492,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -495,6 +508,8 @@ golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrC golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= 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.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= +golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= 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= @@ -561,8 +576,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -584,8 +599,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -640,8 +655,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -650,8 +665,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -664,8 +679,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/internal/pkg/func.go b/internal/pkg/func.go index 32c404b..d07b202 100644 --- a/internal/pkg/func.go +++ b/internal/pkg/func.go @@ -6,8 +6,14 @@ import ( "errors" "fmt" "net/url" + "os" + "path/filepath" + "reflect" "strconv" "strings" + "time" + + jsoniter "github.com/json-iterator/go" ) func JsonStringIgonErr(data interface{}) string { @@ -165,3 +171,190 @@ func SafeReplace(template string, replaceTag string, replacements ...string) (st return template, nil } + +func StructToMapUsingJsoniter(obj interface{}) (map[string]string, error) { + var json = jsoniter.ConfigCompatibleWithStandardLibrary + + // 转换为JSON + jsonBytes, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + // 解析为map[string]interface{} + var tempMap map[string]interface{} + err = json.Unmarshal(jsonBytes, &tempMap) + if err != nil { + return nil, err + } + + // 转换为map[string]string + result := make(map[string]string) + for k, v := range tempMap { + result[k] = fmt.Sprintf("%v", v) + } + + return result, nil +} + +func GetModuleDir() (string, error) { + dir, err := os.Getwd() + if err != nil { + return "", err + } + + for { + modPath := filepath.Join(dir, "go.mod") + if _, err := os.Stat(modPath); err == nil { + return dir, nil // 找到 go.mod + } + + // 向上查找父目录 + parent := filepath.Dir(dir) + if parent == dir { + break // 到达根目录,未找到 + } + dir = parent + } + + return "", fmt.Errorf("go.mod not found in current directory or parents") +} + +// GetCacheDir 用于获取缓存目录路径 +// 如果缓存目录不存在,则会自动创建 +// 返回值: +// - string: 缓存目录的路径 +// - error: 如果获取模块目录失败或创建缓存目录失败,则返回错误信息 +func GetCacheDir() (string, error) { + // 获取模块目录 + modDir, err := GetModuleDir() + if err != nil { + return "", err + } + // 拼接缓存目录路径 + path := fmt.Sprintf("%s/cache", modDir) + // 创建目录(包括所有必要的父目录),权限设置为0755 + err = os.MkdirAll(path, 0755) + if err != nil { + return "", fmt.Errorf("创建目录失败: %w", err) + } + // 返回成功创建的缓存目录路径 + return path, nil +} + +func GetTmplDir() (string, error) { + modDir, err := GetModuleDir() + if err != nil { + return "", err + } + path := fmt.Sprintf("%s/tmpl", modDir) + err = os.MkdirAll(path, 0755) + if err != nil { + return "", fmt.Errorf("创建目录失败: %w", err) + } + return path, nil +} + +// 通用结构体转 Query 参数 +func StructToQuery(obj interface{}) (url.Values, error) { + values := url.Values{} + v := reflect.ValueOf(obj) + t := reflect.TypeOf(obj) + + // 如果是指针,获取指向的值 + if v.Kind() == reflect.Ptr { + v = v.Elem() + t = t.Elem() + } + + // 确保是结构体 + if v.Kind() != reflect.Struct { + return values, fmt.Errorf("expected struct, got %v", v.Kind()) + } + + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + fieldType := t.Field(i) + + // 跳过零值字段(omitempty) + tag := fieldType.Tag.Get("json") + if strings.Contains(tag, "omitempty") && field.IsZero() { + continue + } + + // 获取字段名 + fieldName := getFieldName(fieldType) + if fieldName == "" { + continue + } + + // 处理不同类型的字段 + addFieldToValues(values, fieldName, field) + } + + return values, nil +} + +func getFieldName(field reflect.StructField) string { + tag := field.Tag.Get("json") + if tag != "" { + parts := strings.Split(tag, ",") + if parts[0] != "-" && parts[0] != "" { + return parts[0] + } + if parts[0] == "-" { + return "" // 跳过该字段 + } + } + return field.Name +} + +func addFieldToValues(values url.Values, name string, field reflect.Value) { + if !field.IsValid() || field.IsZero() { + return + } + + switch field.Kind() { + case reflect.String: + values.Add(name, field.String()) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + values.Add(name, strconv.FormatInt(field.Int(), 10)) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + values.Add(name, strconv.FormatUint(field.Uint(), 10)) + + case reflect.Float32, reflect.Float64: + values.Add(name, strconv.FormatFloat(field.Float(), 'f', -1, 64)) + + case reflect.Bool: + values.Add(name, strconv.FormatBool(field.Bool())) + + case reflect.Slice: + // 处理切片,特别是 []string + if field.Type().Elem().Kind() == reflect.String { + for i := 0; i < field.Len(); i++ { + item := field.Index(i).String() + // 特殊处理 ct 字段 + if name == "ct" { + formatted := strings.Replace(item, " ", "+", 1) + if i == 1 && field.Len() >= 2 { + formatted = formatted + ".999" + } + values.Add("ct[]", formatted) + } else { + values.Add(fmt.Sprintf("%s[]", name), item) + } + } + } + + case reflect.Struct: + // 处理 time.Time + if t, ok := field.Interface().(time.Time); ok { + values.Add(name, t.Format("2006-01-02+15:04:05")) + } + + default: + values.Add(name, fmt.Sprintf("%v", field.Interface())) + } +} diff --git a/internal/tools/bbxt/api.go b/internal/tools/bbxt/api.go new file mode 100644 index 0000000..2f62a56 --- /dev/null +++ b/internal/tools/bbxt/api.go @@ -0,0 +1,210 @@ +package bbxt + +import ( + "ai_scheduler/internal/pkg" + "ai_scheduler/internal/pkg/l_request" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "time" +) + +type StatisOursProductLossSumReq struct { + ResellerId int `json:"reseller_id,omitempty"` + Ct []string `json:"ct,omitempty"` +} + +type StatisOursProductLossSumRes struct { + List []*StatisOursProductLossSumResponse `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` +} + +type StatisOursProductLossSumResponse struct { + OursProductId int32 `json:"oursProductId,omitempty"` + OursProductName string `json:"oursProductName,omitempty"` + ResellerName string `json:"resellerName,omitempty"` + ResellerId int32 `json:"resellerId,omitempty"` + Loss float64 `json:"loss,omitempty"` +} + +const Base = "https://reportapi.1688sup.com/api" + +// StatisOursProductLossSumApi 负利润分析 +func StatisOursProductLossSumApi(param *StatisOursProductLossSumReq) (*StatisOursProductLossSumRes, error) { + url := "/dataanalytics/statisOursProductLossSum" + var res StatisOursProductLossSumRes + if err := request(url, param, &res); err != nil { + return nil, err + } + return &res, nil +} + +type GetProfitRankingSumRequest struct { + Ct []string `protobuf:"bytes,1,rep,name=ct,proto3" json:"ct,omitempty"` + Page int32 `protobuf:"varint,3,opt,name=page,proto3" json:"page,omitempty"` + Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + ResellerIds []int32 `protobuf:"varint,5,rep,packed,name=reseller_ids,json=resellerIds,proto3" json:"reseller_ids,omitempty"` +} + +type GetProfitRankingSumResponse struct { + List []*ProfitRankingSumResponse `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` + DataCount int32 `protobuf:"varint,2,opt,name=data_count,json=dataCount,proto3" json:"data_count,omitempty"` +} + +type ProfitRankingSumResponse struct { + // 分销商ID + ResellerId string `protobuf:"bytes,1,opt,name=reseller_id,json=resellerId,proto3" json:"reseller_id,omitempty"` + // 分销商名称 + ResellerName string `protobuf:"bytes,2,opt,name=reseller_name,json=resellerName,proto3" json:"reseller_name,omitempty"` + // 当前利润 + CurrentProfit float64 `protobuf:"fixed64,3,opt,name=current_profit,json=currentProfit,proto3" json:"current_profit,omitempty"` + // 昨日同比利润 + HistoryOneProfit float64 `protobuf:"fixed64,4,opt,name=history_one_profit,json=historyOneProfit,proto3" json:"history_one_profit,omitempty"` + // 上周同比利润 + HistoryTwoProfit float64 `protobuf:"fixed64,5,opt,name=history_two_profit,json=historyTwoProfit,proto3" json:"history_two_profit,omitempty"` + // 昨日同比利润差值 + HistoryOneDiff float64 `protobuf:"fixed64,6,opt,name=history_one_diff,json=historyOneDiff,proto3" json:"history_one_diff,omitempty"` + // 上周同比利润差值 + HistoryTwoDiff float64 `protobuf:"fixed64,7,opt,name=history_two_diff,json=historyTwoDiff,proto3" json:"history_two_diff,omitempty"` +} + +// GetProfitRankingSumApi 利润同比分销商排行榜 +func GetProfitRankingSumApi(param *GetProfitRankingSumRequest) (*GetProfitRankingSumResponse, error) { + url := "/dataanalytics/profitRankingSum" + var res GetProfitRankingSumResponse + if err := request(url, param, &res); err != nil { + return nil, err + } + return &res, nil +} + +type GetStatisOfficialProductSumRequest struct { + Ct []string `protobuf:"bytes,1,rep,name=ct,proto3" json:"ct,omitempty"` + DownwardValue int32 `protobuf:"varint,4,opt,name=downward_value,json=downwardValue,proto3" json:"downward_value,omitempty"` + Page int32 `protobuf:"varint,5,opt,name=page,proto3" json:"page,omitempty"` + Limit int32 `protobuf:"varint,6,opt,name=limit,proto3" json:"limit,omitempty"` + OfficialProductId []int32 `protobuf:"varint,7,rep,packed,name=official_product_id,json=officialProductId,proto3" json:"official_product_id,omitempty"` +} + +type GetStatisOfficialProductSumResponse struct { + OfficialProductSum []*GetStatisOfficialProductSum `protobuf:"bytes,1,rep,name=official_product_sum,json=officialProductSum,proto3" json:"official_product_sum,omitempty"` + DataCount int32 `protobuf:"varint,2,opt,name=data_count,json=dataCount,proto3" json:"data_count,omitempty"` +} + +type GetStatisOfficialProductSum struct { + OfficialProductId int32 `protobuf:"varint,1,opt,name=official_product_id,json=officialProductId,proto3" json:"official_product_id,omitempty"` + OfficialProductName string `protobuf:"bytes,2,opt,name=official_product_name,json=officialProductName,proto3" json:"official_product_name,omitempty"` + CurrentNum int32 `protobuf:"varint,3,opt,name=current_num,json=currentNum,proto3" json:"current_num,omitempty"` + HistoryOneNum int32 `protobuf:"varint,4,opt,name=history_one_num,json=historyOneNum,proto3" json:"history_one_num,omitempty"` + HistoryTwoNum int32 `protobuf:"varint,5,opt,name=history_two_num,json=historyTwoNum,proto3" json:"history_two_num,omitempty"` + HistoryOneDiff int32 `protobuf:"varint,6,opt,name=history_one_diff,json=historyOneDiff,proto3" json:"history_one_diff,omitempty"` + HistoryTwoDiff int32 `protobuf:"varint,7,opt,name=history_two_diff,json=historyTwoDiff,proto3" json:"history_two_diff,omitempty"` +} + +// GetStatisOfficialProductSumApi 销量同比分析 +func GetStatisOfficialProductSumApi(param *GetStatisOfficialProductSumRequest) (*GetStatisOfficialProductSumResponse, error) { + url := "/dataanalytics/statisOfficialProduct" + var res GetStatisOfficialProductSumResponse + if err := request(url, param, &res); err != nil { + return nil, err + } + return &res, nil +} + +type GetStatisOfficialProductSumDeclineResponse struct { + OfficialProductSumDecline []*GetStatisOfficialProductSumDecline `protobuf:"bytes,1,rep,name=official_product_sum_decline,json=officialProductSumDecline,proto3" json:"official_product_sum_decline,omitempty"` + DataCount int32 `protobuf:"varint,2,opt,name=data_count,json=dataCount,proto3" json:"data_count,omitempty"` +} + +type GetStatisOfficialProductSumDecline struct { + ResellerId int32 `protobuf:"varint,1,opt,name=reseller_id,json=resellerId,proto3" json:"reseller_id,omitempty"` + OfficialProductId int32 `protobuf:"varint,2,opt,name=official_product_id,json=officialProductId,proto3" json:"official_product_id,omitempty"` + OfficialProductName string `protobuf:"bytes,3,opt,name=official_product_name,json=officialProductName,proto3" json:"official_product_name,omitempty"` + ResellerName string `protobuf:"bytes,4,opt,name=reseller_name,json=resellerName,proto3" json:"reseller_name,omitempty"` + CurrentNum int32 `protobuf:"varint,5,opt,name=current_num,json=currentNum,proto3" json:"current_num,omitempty"` + HistoryOneNum int32 `protobuf:"varint,6,opt,name=history_one_num,json=historyOneNum,proto3" json:"history_one_num,omitempty"` + HistoryTwoNum int32 `protobuf:"varint,7,opt,name=history_two_num,json=historyTwoNum,proto3" json:"history_two_num,omitempty"` + HistoryOneDiff int32 `protobuf:"varint,8,opt,name=history_one_diff,json=historyOneDiff,proto3" json:"history_one_diff,omitempty"` + HistoryTwoDiff int32 `protobuf:"varint,9,opt,name=history_two_diff,json=historyTwoDiff,proto3" json:"history_two_diff,omitempty"` +} + +// GetStatisOfficialProductSumDeclineApi 销量同比分析 +func GetStatisOfficialProductSumDeclineApi(param *GetStatisOfficialProductSumRequest) (*GetStatisOfficialProductSumDeclineResponse, error) { + url := "/dataanalytics/statisOfficialProductDecline" + var res GetStatisOfficialProductSumDeclineResponse + if err := request(url, param, &res); err != nil { + return nil, err + } + return &res, nil +} + +type resCode struct { + Code int `json:"code"` + Data json.RawMessage `json:"data"` + Error string `json:"error"` +} + +func request(url string, reqData interface{}, resData interface{}) error { + + reqParam, err := pkg.StructToQuery(reqData) + if err != nil { + return err + } + + req := &l_request.Request{ + Url: Base + url + "?" + customEncode(reqParam), + Method: http.MethodGet, + } + res, err := req.Send() + if res.StatusCode != http.StatusOK { + return fmt.Errorf("request failed, status code: %d,resion: %s", res.StatusCode, res.Reason) + } + var code resCode + if err = json.Unmarshal(res.Content, &code); err != nil { + return fmt.Errorf("返回结构异常:%s", string(res.Content)) + } + if code.Code != 200 { + return fmt.Errorf("返回状态异常:%s", string(code.Error)) + } + if err = json.Unmarshal(code.Data, resData); err != nil { + return fmt.Errorf("返回数据异常:%s", string(res.Content)) + } + return nil +} + +func formatCtToQueryString(ct []string) string { + if len(ct) != 2 { + return "" + } + + values := url.Values{} + + // 第一个时间(开始时间) + startTime, err := time.Parse("2006-01-02 15:04:05", ct[0]) + if err == nil { + // 保持原样 + values.Add("ct[]", startTime.Format("2006-01-02+15:04:05")) + } + + // 第二个时间(结束时间) + endTime, err := time.Parse("2006-01-02 15:04:05", ct[1]) + if err == nil { + // 添加毫秒 + endTimeWithMs := endTime.Add(999 * time.Millisecond) + values.Add("ct[]", endTimeWithMs.Format("2006-01-02+15:04:05.000")) + } + + return values.Encode() +} + +func customEncode(params url.Values) string { + encoded := params.Encode() + + // 解码我们想要保留的字符 + encoded = strings.ReplaceAll(encoded, "%5B", "[") // 恢复 [ + encoded = strings.ReplaceAll(encoded, "%5D", "]") // 恢复 ] + encoded = strings.ReplaceAll(encoded, "%2B", "+") // 恢复 + + + return encoded +} diff --git a/internal/tools/bbxt/bbxt.go b/internal/tools/bbxt/bbxt.go new file mode 100644 index 0000000..1cc5f42 --- /dev/null +++ b/internal/tools/bbxt/bbxt.go @@ -0,0 +1,133 @@ +package bbxt + +import ( + "ai_scheduler/internal/pkg" + "fmt" + "reflect" + "time" + + "github.com/xuri/excelize/v2" +) + +type BbxtTools struct { + cacheDir string + excelTempDir string + ct []string +} + +func NewBbxtTools() (*BbxtTools, error) { + cache, err := pkg.GetCacheDir() + if err != nil { + return nil, err + } + tempDir, err := pkg.GetTmplDir() + if err != nil { + return nil, err + } + now := time.Now() + return &BbxtTools{ + cacheDir: cache, + excelTempDir: fmt.Sprintf("%s/excel_temp", tempDir), + ct: []string{ + time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Format("2006-01-02 15:04:05"), + time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Format("2006-01-02 15:04:05"), + }, + }, nil +} + +func (b *BbxtTools) DailyReport() (err error) { + err = b.StatisOursProductLossSumTotal() + if err != nil { + return + } + return +} + +// OursProductLossSum 负利润分析 +func (b *BbxtTools) StatisOursProductLossSumTotal() (err error) { + data, err := StatisOursProductLossSumApi(&StatisOursProductLossSumReq{ + Ct: b.ct, + }) + if err != nil { + return + } + var ( + total [][]string + gt [][]string + ) + + for _, v := range data.List { + if v.Loss <= -100 { + total = append(total, []string{ + v.OursProductName, + fmt.Sprintf("%.2f", v.Loss), + }) + } + if v.Loss <= -500 { + gt = append(gt, []string{ + v.OursProductName, + fmt.Sprintf("%.2f", v.Loss), + }) + } + } + //总量生成excel + if len(total) > 0 { + filePath := b.cacheDir + "/kshj_total" + fmt.Sprintf("%d", time.Now().Unix()) + ".xlsx" + err = b.SimpleFillExcel(b.excelTempDir+"/"+"kshj_total.xlsx", filePath, total) + } + return err +} + +// 最简单的通用函数 +func (b *BbxtTools) SimpleFillExcel(templatePath, outputPath string, dataSlice interface{}) error { + // 1. 打开模板 + f, err := excelize.OpenFile(templatePath) + if err != nil { + return err + } + defer f.Close() + + sheet := f.GetSheetName(0) + + // 2. 反射获取切片数据 + v := reflect.ValueOf(dataSlice) + if v.Kind() != reflect.Slice { + return fmt.Errorf("dataSlice must be a slice") + } + + // 3. 从第2行开始填充 + row := 2 + for i := 0; i < v.Len(); i++ { + item := v.Index(i).Interface() + currentRow := row + i + + // 4. 将item转换为一行数据 + var rowData []interface{} + + // 如果是切片 + if reflect.TypeOf(item).Kind() == reflect.Slice { + itemV := reflect.ValueOf(item) + for j := 0; j < itemV.Len(); j++ { + rowData = append(rowData, itemV.Index(j).Interface()) + } + } else if reflect.TypeOf(item).Kind() == reflect.Struct { + itemV := reflect.ValueOf(item) + for j := 0; j < itemV.NumField(); j++ { + if itemV.Field(j).CanInterface() { + rowData = append(rowData, itemV.Field(j).Interface()) + } + } + } else { + rowData = []interface{}{item} + } + + // 5. 填充到Excel + for col, value := range rowData { + cell := fmt.Sprintf("%c%d", 'A'+col, currentRow) + f.SetCellValue(sheet, cell, value) + } + } + + // 6. 保存 + return f.SaveAs(outputPath) +} diff --git a/internal/tools/bbxt/bbxt_test.go b/internal/tools/bbxt/bbxt_test.go new file mode 100644 index 0000000..80e6b29 --- /dev/null +++ b/internal/tools/bbxt/bbxt_test.go @@ -0,0 +1,14 @@ +package bbxt + +import "testing" + +func Test_StatisOursProductLossSumApiTotal(t *testing.T) { + o, err := NewBbxtTools() + if err != nil { + panic(err) + } + err = o.StatisOursProductLossSumTotal() + + t.Log(err) + +} diff --git a/tmpl/excel_temp/kshj_total.xlsx b/tmpl/excel_temp/kshj_total.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2035ef939d202a1570c39cb8884ac4a456927e40 GIT binary patch literal 10313 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 M;6ZY96kxso2YYrJCjbBd literal 0 HcmV?d00001