新增: AI工单处理工作台 v1.0
- Go Gin 后端 (19个源文件): 认证、工单CRUD、GLM AI分析、状态流转、备注、操作日志 - Arco Design Vue 前端: 登录、工单列表/详情/创建、AI分析触发与确认 - MySQL 5表: ticket_user/ticket_info/ticket_ai_analysis/ticket_operation_log/ticket_note - 部署: tk.1216.top HTTPS, Nginx反代
This commit is contained in:
56
backend/go.mod
Normal file
56
backend/go.mod
Normal file
@@ -0,0 +1,56 @@
|
||||
module github.com/casehub/ticket-workbench
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/spf13/viper v1.18.2
|
||||
gorm.io/driver/mysql v1.5.6
|
||||
gorm.io/gorm v1.25.7
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // 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
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.16.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
141
backend/go.sum
Normal file
141
backend/go.sum
Normal file
@@ -0,0 +1,141 @@
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
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.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
|
||||
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
43
backend/internal/config/config.go
Normal file
43
backend/internal/config/config.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Database DatabaseConfig `mapstructure:"db"`
|
||||
GLM GLMConfig `mapstructure:"glm"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
DBName string `mapstructure:"dbname"`
|
||||
}
|
||||
|
||||
type GLMConfig struct {
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
BaseURL string `mapstructure:"base_url"`
|
||||
Model string `mapstructure:"model"`
|
||||
}
|
||||
|
||||
func Load(path string) (*Config, error) {
|
||||
v := viper.New()
|
||||
v.SetConfigFile(path)
|
||||
v.SetConfigType("yaml")
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cfg Config
|
||||
if err := v.Unmarshal(&cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
28
backend/internal/dto/common.go
Normal file
28
backend/internal/dto/common.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package dto
|
||||
|
||||
type Response struct {
|
||||
Success bool `json:"success"`
|
||||
Retcode int `json:"retcode"`
|
||||
Retinfo string `json:"retinfo"`
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
}
|
||||
|
||||
func Success(data interface{}) Response {
|
||||
return Response{Success: true, Retcode: 0, Retinfo: "", Result: data}
|
||||
}
|
||||
|
||||
func Error(code int, msg string) Response {
|
||||
return Response{Success: false, Retcode: code, Retinfo: msg}
|
||||
}
|
||||
|
||||
func Fail(msg string) Response {
|
||||
return Response{Success: false, Retcode: -1, Retinfo: msg}
|
||||
}
|
||||
|
||||
type UserSession struct {
|
||||
Userid int `json:"userid"`
|
||||
Username string `json:"username"`
|
||||
Account string `json:"account"`
|
||||
Role int16 `json:"role"`
|
||||
Team string `json:"team"`
|
||||
}
|
||||
49
backend/internal/dto/ticket.go
Normal file
49
backend/internal/dto/ticket.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package dto
|
||||
|
||||
type LoginRequest struct {
|
||||
Account string `json:"account" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type CreateTicketRequest struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
Contactname string `json:"contactname" binding:"required"`
|
||||
Contactphone string `json:"contactphone" binding:"required"`
|
||||
Source string `json:"source"`
|
||||
Category string `json:"category"`
|
||||
Priority int16 `json:"priority"`
|
||||
}
|
||||
|
||||
type UpdateTicketRequest struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Contactname string `json:"contactname"`
|
||||
Contactphone string `json:"contactphone"`
|
||||
Category string `json:"category"`
|
||||
Priority int16 `json:"priority"`
|
||||
Handlerid *int `json:"handlerid"`
|
||||
}
|
||||
|
||||
type UpdateStatusRequest struct {
|
||||
Status int16 `json:"status" binding:"required"`
|
||||
}
|
||||
|
||||
type TicketListQuery struct {
|
||||
Status int16 `form:"status"`
|
||||
Category string `form:"category"`
|
||||
Priority int16 `form:"priority"`
|
||||
Keyword string `form:"keyword"`
|
||||
Page int `form:"page"`
|
||||
PageSize int `form:"pageSize"`
|
||||
}
|
||||
|
||||
type ConfirmAnalysisRequest struct {
|
||||
Category string `json:"category"`
|
||||
Priority int16 `json:"priority"`
|
||||
Summary string `json:"summary"`
|
||||
}
|
||||
|
||||
type AddNoteRequest struct {
|
||||
Content string `json:"content" binding:"required"`
|
||||
}
|
||||
95
backend/internal/handler/analysis_handler.go
Normal file
95
backend/internal/handler/analysis_handler.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/casehub/ticket-workbench/internal/config"
|
||||
"github.com/casehub/ticket-workbench/internal/dto"
|
||||
"github.com/casehub/ticket-workbench/internal/middleware"
|
||||
"github.com/casehub/ticket-workbench/internal/model"
|
||||
"github.com/casehub/ticket-workbench/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func AnalyzeTicket(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
var ticket model.TicketInfo
|
||||
if err := db.Where("ticketid = ?", id).First(&ticket).Error; err != nil {
|
||||
c.JSON(200, dto.Fail("工单不存在"))
|
||||
return
|
||||
}
|
||||
|
||||
analysis, err := service.AnalyzeTicket(db, id, cfg.GLM.APIKey, cfg.GLM.BaseURL, cfg.GLM.Model,
|
||||
ticket.Title, ticket.Content, ticket.Contactname, ticket.Contactphone)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("AI分析失败: "+err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(analysis))
|
||||
}
|
||||
}
|
||||
|
||||
func GetAnalysis(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
analyses, err := service.GetAnalysisByTicketID(db, id)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("查询失败"))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(analyses))
|
||||
}
|
||||
}
|
||||
|
||||
func ConfirmAnalysis(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
ticketid, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.ConfirmAnalysisRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
user := middleware.GetCurrentUser(c)
|
||||
if user == nil {
|
||||
c.JSON(200, dto.Fail("未登录"))
|
||||
return
|
||||
}
|
||||
|
||||
var analysis model.TicketAiAnalysis
|
||||
if err := db.Where("ticketid = ? AND confirmed = 0", ticketid).Order("createtime DESC").First(&analysis).Error; err != nil {
|
||||
c.JSON(200, dto.Fail("未找到待确认的分析结果"))
|
||||
return
|
||||
}
|
||||
|
||||
err = service.ConfirmAnalysis(db, analysis.Analysisid, req.Category, req.Priority, req.Summary, user.Userid)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("确认失败"))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(nil))
|
||||
}
|
||||
}
|
||||
64
backend/internal/handler/auth_handler.go
Normal file
64
backend/internal/handler/auth_handler.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/casehub/ticket-workbench/internal/dto"
|
||||
"github.com/casehub/ticket-workbench/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Login(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req dto.LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
user, sessionID, err := service.Login(db, req.Account, req.Password)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(map[string]interface{}{
|
||||
"token": sessionID,
|
||||
"user": map[string]interface{}{
|
||||
"userid": user.Userid,
|
||||
"username": user.Username,
|
||||
"account": user.Account,
|
||||
"role": user.Role,
|
||||
"team": user.Team,
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
func Logout() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
sessionID := c.GetHeader("Authorization")
|
||||
if sessionID == "" {
|
||||
sessionID = c.GetHeader("jsessionid")
|
||||
}
|
||||
service.Logout(sessionID)
|
||||
c.JSON(200, dto.Success(nil))
|
||||
}
|
||||
}
|
||||
|
||||
func UserInfo() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
userid, _ := c.Get("userid")
|
||||
username, _ := c.Get("username")
|
||||
account := c.GetString("account")
|
||||
role, _ := c.Get("role")
|
||||
team, _ := c.Get("team")
|
||||
|
||||
c.JSON(200, dto.Success(map[string]interface{}{
|
||||
"userid": userid,
|
||||
"username": username,
|
||||
"account": account,
|
||||
"role": role,
|
||||
"team": team,
|
||||
}))
|
||||
}
|
||||
}
|
||||
61
backend/internal/handler/note_handler.go
Normal file
61
backend/internal/handler/note_handler.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/casehub/ticket-workbench/internal/dto"
|
||||
"github.com/casehub/ticket-workbench/internal/middleware"
|
||||
"github.com/casehub/ticket-workbench/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func ListNotes(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
notes, err := service.ListNotes(db, id)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("查询失败"))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(notes))
|
||||
}
|
||||
}
|
||||
|
||||
func AddNote(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AddNoteRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
user := middleware.GetCurrentUser(c)
|
||||
if user == nil {
|
||||
c.JSON(200, dto.Fail("未登录"))
|
||||
return
|
||||
}
|
||||
|
||||
note, err := service.AddNote(db, id, user.Userid, req.Content)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("添加失败"))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(note))
|
||||
}
|
||||
}
|
||||
160
backend/internal/handler/ticket_handler.go
Normal file
160
backend/internal/handler/ticket_handler.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/casehub/ticket-workbench/internal/dto"
|
||||
"github.com/casehub/ticket-workbench/internal/middleware"
|
||||
"github.com/casehub/ticket-workbench/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func ListTickets(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var query dto.TicketListQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
if query.Page <= 0 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize <= 0 {
|
||||
query.PageSize = 20
|
||||
}
|
||||
|
||||
result, err := service.ListTickets(db, query.Status, query.Category, query.Priority, query.Keyword, query.Page, query.PageSize)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("查询失败"))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(result))
|
||||
}
|
||||
}
|
||||
|
||||
func GetTicket(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
ticket, err := service.GetTicketByID(db, id)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("工单不存在"))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(ticket))
|
||||
}
|
||||
}
|
||||
|
||||
func CreateTicket(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var req dto.CreateTicketRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
user := middleware.GetCurrentUser(c)
|
||||
if user == nil {
|
||||
c.JSON(200, dto.Fail("未登录"))
|
||||
return
|
||||
}
|
||||
|
||||
ticket, err := service.CreateTicket(db, req.Title, req.Content, req.Contactname, req.Contactphone, req.Source, req.Category, req.Priority, user.Userid)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("创建失败"))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(ticket))
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateTicket(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.UpdateTicketRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
user := middleware.GetCurrentUser(c)
|
||||
if user == nil {
|
||||
c.JSON(200, dto.Fail("未登录"))
|
||||
return
|
||||
}
|
||||
|
||||
err = service.UpdateTicket(db, id, req.Title, req.Content, req.Contactname, req.Contactphone, req.Category, req.Priority, req.Handlerid, user.Userid)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("更新失败"))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(nil))
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateTicketStatus(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.UpdateStatusRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
user := middleware.GetCurrentUser(c)
|
||||
if user == nil {
|
||||
c.JSON(200, dto.Fail("未登录"))
|
||||
return
|
||||
}
|
||||
|
||||
err = service.UpdateTicketStatus(db, id, req.Status, user.Userid)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("更新失败"))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(nil))
|
||||
}
|
||||
}
|
||||
|
||||
func GetOperationLogs(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("参数错误"))
|
||||
return
|
||||
}
|
||||
|
||||
logs, err := service.GetOperationLogs(db, id)
|
||||
if err != nil {
|
||||
c.JSON(200, dto.Fail("查询失败"))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, dto.Success(logs))
|
||||
}
|
||||
}
|
||||
58
backend/internal/middleware/auth.go
Normal file
58
backend/internal/middleware/auth.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/casehub/ticket-workbench/internal/model"
|
||||
"github.com/casehub/ticket-workbench/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Auth(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
sessionID := extractSessionID(c)
|
||||
if sessionID == "" {
|
||||
c.JSON(200, map[string]interface{}{"success": false, "retcode": -1, "retinfo": "未登录"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
user := service.GetUserBySession(sessionID)
|
||||
if user == nil {
|
||||
c.JSON(200, map[string]interface{}{"success": false, "retcode": -1, "retinfo": "登录已过期"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("userid", user.Userid)
|
||||
c.Set("username", user.Username)
|
||||
c.Set("role", user.Role)
|
||||
c.Set("team", user.Team)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func extractSessionID(c *gin.Context) string {
|
||||
if s := c.GetHeader("Authorization"); s != "" {
|
||||
return s
|
||||
}
|
||||
if s := c.GetHeader("jsessionid"); s != "" {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetCurrentUser(c *gin.Context) *model.TicketUser {
|
||||
userid, exists := c.Get("userid")
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
username, _ := c.Get("username")
|
||||
role, _ := c.Get("role")
|
||||
team, _ := c.Get("team")
|
||||
return &model.TicketUser{
|
||||
Userid: userid.(int),
|
||||
Username: username.(string),
|
||||
Role: role.(int16),
|
||||
Team: team.(string),
|
||||
}
|
||||
}
|
||||
21
backend/internal/middleware/cors.go
Normal file
21
backend/internal/middleware/cors.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CORS() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, jsessionid")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
21
backend/internal/model/analysis.go
Normal file
21
backend/internal/model/analysis.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type TicketAiAnalysis struct {
|
||||
Analysisid int `gorm:"primaryKey;autoIncrement" json:"analysisid"`
|
||||
Ticketid int `gorm:"not null" json:"ticketid"`
|
||||
Category string `gorm:"size:32" json:"category"`
|
||||
Priority int16 `json:"priority"`
|
||||
Summary string `gorm:"type:text" json:"summary"`
|
||||
Suggestrole string `gorm:"size:64" json:"suggestrole"`
|
||||
Rawresponse string `gorm:"type:text" json:"rawresponse"`
|
||||
Confirmed int8 `gorm:"default:0" json:"confirmed"`
|
||||
Confirmedby *int `json:"confirmedby"`
|
||||
Confirmedat *time.Time `json:"confirmedat"`
|
||||
Createtime time.Time `json:"createtime"`
|
||||
}
|
||||
|
||||
func (TicketAiAnalysis) TableName() string {
|
||||
return "ticket_ai_analysis"
|
||||
}
|
||||
15
backend/internal/model/note.go
Normal file
15
backend/internal/model/note.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type TicketNote struct {
|
||||
Noteid int `gorm:"primaryKey;autoIncrement" json:"noteid"`
|
||||
Ticketid int `gorm:"not null" json:"ticketid"`
|
||||
Authorid int `json:"authorid"`
|
||||
Content string `gorm:"type:text;not null" json:"content"`
|
||||
Createtime time.Time `json:"createtime"`
|
||||
}
|
||||
|
||||
func (TicketNote) TableName() string {
|
||||
return "ticket_note"
|
||||
}
|
||||
16
backend/internal/model/operation_log.go
Normal file
16
backend/internal/model/operation_log.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type TicketOperationLog struct {
|
||||
Logid int `gorm:"primaryKey;autoIncrement" json:"logid"`
|
||||
Ticketid int `gorm:"not null" json:"ticketid"`
|
||||
Operatorid int `json:"operatorid"`
|
||||
Action string `gorm:"size:32;not null" json:"action"`
|
||||
Detail string `gorm:"type:text" json:"detail"`
|
||||
Createtime time.Time `json:"createtime"`
|
||||
}
|
||||
|
||||
func (TicketOperationLog) TableName() string {
|
||||
return "ticket_operation_log"
|
||||
}
|
||||
24
backend/internal/model/ticket.go
Normal file
24
backend/internal/model/ticket.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type TicketInfo struct {
|
||||
Ticketid int `gorm:"primaryKey;autoIncrement" json:"ticketid"`
|
||||
Ticketno string `gorm:"size:32;uniqueIndex" json:"ticketno"`
|
||||
Title string `gorm:"size:255;not null" json:"title"`
|
||||
Content string `gorm:"type:text" json:"content"`
|
||||
Contactname string `gorm:"size:64" json:"contactname"`
|
||||
Contactphone string `gorm:"size:20" json:"contactphone"`
|
||||
Source string `gorm:"size:20;default:web" json:"source"`
|
||||
Submitterid int `json:"submitterid"`
|
||||
Category string `gorm:"size:32" json:"category"`
|
||||
Priority int16 `gorm:"default:2" json:"priority"`
|
||||
Handlerid *int `json:"handlerid"`
|
||||
Status int16 `gorm:"default:0" json:"status"`
|
||||
Createtime time.Time `json:"createtime"`
|
||||
Updatetime time.Time `json:"updatetime"`
|
||||
}
|
||||
|
||||
func (TicketInfo) TableName() string {
|
||||
return "ticket_info"
|
||||
}
|
||||
19
backend/internal/model/user.go
Normal file
19
backend/internal/model/user.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type TicketUser struct {
|
||||
Userid int `gorm:"primaryKey;autoIncrement" json:"userid"`
|
||||
Username string `gorm:"size:64" json:"username"`
|
||||
Account string `gorm:"size:64;uniqueIndex" json:"account"`
|
||||
Password string `gorm:"size:64" json:"-"`
|
||||
Role int16 `gorm:"default:20" json:"role"`
|
||||
Team string `gorm:"size:32" json:"team"`
|
||||
Status int16 `gorm:"default:1" json:"status"`
|
||||
Createtime time.Time `json:"createtime"`
|
||||
Updatetime time.Time `json:"updatetime"`
|
||||
}
|
||||
|
||||
func (TicketUser) TableName() string {
|
||||
return "ticket_user"
|
||||
}
|
||||
161
backend/internal/service/analysis_service.go
Normal file
161
backend/internal/service/analysis_service.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casehub/ticket-workbench/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type GLMRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []Message `json:"messages"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type GLMResponse struct {
|
||||
Choices []Choice `json:"choices"`
|
||||
}
|
||||
|
||||
type Choice struct {
|
||||
Message Message `json:"message"`
|
||||
}
|
||||
|
||||
type AnalysisResult struct {
|
||||
Category string `json:"category"`
|
||||
Priority json.Number `json:"priority"`
|
||||
Summary string `json:"summary"`
|
||||
SuggestRole string `json:"suggest_role"`
|
||||
}
|
||||
|
||||
func AnalyzeTicket(db *gorm.DB, ticketid int, apikey, baseURL, glmModel, title, content, contactname, contactphone string) (*model.TicketAiAnalysis, error) {
|
||||
prompt := fmt.Sprintf(`你是一个客服工单分析助手。请分析以下工单内容,返回JSON格式的分析结果:
|
||||
{
|
||||
"category": "分类(refund/login/invoice/logistics/account/inquiry/other)",
|
||||
"priority": "优先级(0=P0紧急,1=P1高,2=P2中,3=P3低)",
|
||||
"summary": "一句话摘要",
|
||||
"suggest_role": "建议处理团队(refund_team/tech_support/finance_team/logistics_team/customer_service)"
|
||||
}
|
||||
|
||||
工单标题: %s
|
||||
工单内容: %s
|
||||
联系人: %s
|
||||
联系电话: %s
|
||||
|
||||
请只返回JSON,不要其他内容。`, title, content, contactname, contactphone)
|
||||
|
||||
reqBody := GLMRequest{
|
||||
Model: glmModel,
|
||||
Messages: []Message{
|
||||
{Role: "system", Content: "你是一个专业的客服工单分析助手,擅长对工单进行分类、优先级判断和智能分派。"},
|
||||
{Role: "user", Content: prompt},
|
||||
},
|
||||
}
|
||||
|
||||
jsonData, _ := json.Marshal(reqBody)
|
||||
|
||||
httpReq, err := http.NewRequest("POST", baseURL+"/chat/completions", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("Authorization", "Bearer "+apikey)
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
var glmResp GLMResponse
|
||||
if err := json.Unmarshal(body, &glmResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(glmResp.Choices) == 0 {
|
||||
return nil, fmt.Errorf("AI返回为空")
|
||||
}
|
||||
|
||||
// Clean markdown code block wrapping from AI response
|
||||
aiContent := strings.TrimSpace(glmResp.Choices[0].Message.Content)
|
||||
aiContent = strings.TrimPrefix(aiContent, "```json")
|
||||
aiContent = strings.TrimPrefix(aiContent, "```")
|
||||
aiContent = strings.TrimSuffix(aiContent, "```")
|
||||
aiContent = strings.TrimSpace(aiContent)
|
||||
|
||||
var result AnalysisResult
|
||||
if err := json.Unmarshal([]byte(aiContent), &result); err != nil {
|
||||
return nil, fmt.Errorf("解析AI响应失败: %v, 原始内容: %s", err, aiContent)
|
||||
}
|
||||
|
||||
priority := int16(2) // default P2
|
||||
if n, err := result.Priority.Int64(); err == nil {
|
||||
priority = int16(n)
|
||||
}
|
||||
|
||||
analysis := &model.TicketAiAnalysis{
|
||||
Ticketid: ticketid,
|
||||
Category: result.Category,
|
||||
Priority: priority,
|
||||
Summary: result.Summary,
|
||||
Suggestrole: result.SuggestRole,
|
||||
Rawresponse: glmResp.Choices[0].Message.Content,
|
||||
Confirmed: 0,
|
||||
Createtime: time.Now(),
|
||||
}
|
||||
|
||||
if err := db.Create(analysis).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
AddOperationLog(db, ticketid, 0, "analyze", "AI分析: "+result.Summary)
|
||||
|
||||
return analysis, nil
|
||||
}
|
||||
|
||||
func GetAnalysisByTicketID(db *gorm.DB, ticketid int) ([]model.TicketAiAnalysis, error) {
|
||||
var analyses []model.TicketAiAnalysis
|
||||
err := db.Where("ticketid = ?", ticketid).Order("createtime DESC").Find(&analyses).Error
|
||||
return analyses, err
|
||||
}
|
||||
|
||||
func ConfirmAnalysis(db *gorm.DB, analysisid int, category string, priority int16, summary string, confirmedby int) error {
|
||||
updates := map[string]interface{}{
|
||||
"confirmed": 1,
|
||||
"confirmedby": confirmedby,
|
||||
"confirmedat": time.Now(),
|
||||
}
|
||||
if category != "" {
|
||||
updates["category"] = category
|
||||
}
|
||||
if priority >= 0 {
|
||||
updates["priority"] = priority
|
||||
}
|
||||
if summary != "" {
|
||||
updates["summary"] = summary
|
||||
}
|
||||
|
||||
if err := db.Model(&model.TicketAiAnalysis{}).Where("analysisid = ?", analysisid).Updates(updates).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var analysis model.TicketAiAnalysis
|
||||
db.Where("analysisid = ?", analysisid).First(&analysis)
|
||||
AddOperationLog(db, analysis.Ticketid, confirmedby, "confirm_analysis", "确认AI分析结果")
|
||||
|
||||
return nil
|
||||
}
|
||||
46
backend/internal/service/auth_service.go
Normal file
46
backend/internal/service/auth_service.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/casehub/ticket-workbench/internal/model"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var sessions = make(map[string]*model.TicketUser)
|
||||
|
||||
func MD5Hash(text string) string {
|
||||
hash := md5.Sum([]byte(text))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func Login(db *gorm.DB, account, password string) (*model.TicketUser, string, error) {
|
||||
var user model.TicketUser
|
||||
err := db.Where("account = ? AND status = 1", account).First(&user).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, "", fmt.Errorf("账号或密码错误")
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if user.Password != MD5Hash(password) {
|
||||
return nil, "", fmt.Errorf("账号或密码错误")
|
||||
}
|
||||
|
||||
sessionID := uuid.New().String()
|
||||
sessions[sessionID] = &user
|
||||
|
||||
return &user, sessionID, nil
|
||||
}
|
||||
|
||||
func Logout(sessionID string) {
|
||||
delete(sessions, sessionID)
|
||||
}
|
||||
|
||||
func GetUserBySession(sessionID string) *model.TicketUser {
|
||||
return sessions[sessionID]
|
||||
}
|
||||
31
backend/internal/service/note_service.go
Normal file
31
backend/internal/service/note_service.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/casehub/ticket-workbench/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func ListNotes(db *gorm.DB, ticketid int) ([]model.TicketNote, error) {
|
||||
var notes []model.TicketNote
|
||||
err := db.Where("ticketid = ?", ticketid).Order("createtime ASC").Find(¬es).Error
|
||||
return notes, err
|
||||
}
|
||||
|
||||
func AddNote(db *gorm.DB, ticketid, authorid int, content string) (*model.TicketNote, error) {
|
||||
note := &model.TicketNote{
|
||||
Ticketid: ticketid,
|
||||
Authorid: authorid,
|
||||
Content: content,
|
||||
Createtime: time.Now(),
|
||||
}
|
||||
|
||||
if err := db.Create(note).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
AddOperationLog(db, ticketid, authorid, "note", "添加备注: "+content)
|
||||
|
||||
return note, nil
|
||||
}
|
||||
149
backend/internal/service/ticket_service.go
Normal file
149
backend/internal/service/ticket_service.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/casehub/ticket-workbench/internal/model"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type TicketListResult struct {
|
||||
Total int64 `json:"total"`
|
||||
Rows []model.TicketInfo `json:"rows"`
|
||||
}
|
||||
|
||||
func ListTickets(db *gorm.DB, status int16, category string, priority int16, keyword string, page, pageSize int) (*TicketListResult, error) {
|
||||
var total int64
|
||||
var tickets []model.TicketInfo
|
||||
|
||||
query := db.Model(&model.TicketInfo{})
|
||||
|
||||
if status >= 0 {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
if category != "" {
|
||||
query = query.Where("category = ?", category)
|
||||
}
|
||||
if priority >= 0 {
|
||||
query = query.Where("priority = ?", priority)
|
||||
}
|
||||
if keyword != "" {
|
||||
query = query.Where("title LIKE ? OR content LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Offset(offset).Limit(pageSize).Order("createtime DESC").Find(&tickets).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TicketListResult{Total: total, Rows: tickets}, nil
|
||||
}
|
||||
|
||||
func GetTicketByID(db *gorm.DB, ticketid int) (*model.TicketInfo, error) {
|
||||
var ticket model.TicketInfo
|
||||
err := db.Where("ticketid = ?", ticketid).First(&ticket).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ticket, nil
|
||||
}
|
||||
|
||||
func CreateTicket(db *gorm.DB, title, content, contactname, contactphone, source, category string, priority int16, submitterid int) (*model.TicketInfo, error) {
|
||||
ticketno := fmt.Sprintf("TKT%s", uuid.New().String()[:8])
|
||||
now := time.Now()
|
||||
|
||||
ticket := &model.TicketInfo{
|
||||
Ticketno: ticketno,
|
||||
Title: title,
|
||||
Content: content,
|
||||
Contactname: contactname,
|
||||
Contactphone: contactphone,
|
||||
Source: source,
|
||||
Category: category,
|
||||
Priority: priority,
|
||||
Submitterid: submitterid,
|
||||
Status: 0,
|
||||
Createtime: now,
|
||||
Updatetime: now,
|
||||
}
|
||||
|
||||
if err := db.Create(ticket).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
AddOperationLog(db, ticket.Ticketid, submitterid, "create", "创建工单")
|
||||
|
||||
return ticket, nil
|
||||
}
|
||||
|
||||
func UpdateTicket(db *gorm.DB, ticketid int, title, content, contactname, contactphone, category string, priority int16, handlerid *int, operatorid int) error {
|
||||
updates := map[string]interface{}{
|
||||
"updatetime": time.Now(),
|
||||
}
|
||||
if title != "" {
|
||||
updates["title"] = title
|
||||
}
|
||||
if content != "" {
|
||||
updates["content"] = content
|
||||
}
|
||||
if contactname != "" {
|
||||
updates["contactname"] = contactname
|
||||
}
|
||||
if contactphone != "" {
|
||||
updates["contactphone"] = contactphone
|
||||
}
|
||||
if category != "" {
|
||||
updates["category"] = category
|
||||
}
|
||||
if priority >= 0 {
|
||||
updates["priority"] = priority
|
||||
}
|
||||
if handlerid != nil {
|
||||
updates["handlerid"] = handlerid
|
||||
}
|
||||
|
||||
if err := db.Model(&model.TicketInfo{}).Where("ticketid = ?", ticketid).Updates(updates).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
AddOperationLog(db, ticketid, operatorid, "update", "更新工单信息")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateTicketStatus(db *gorm.DB, ticketid int, status int16, operatorid int) error {
|
||||
if err := db.Model(&model.TicketInfo{}).Where("ticketid = ?", ticketid).Updates(map[string]interface{}{
|
||||
"status": status,
|
||||
"updatetime": time.Now(),
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statusText := map[int16]string{0: "待处理", 1: "分析中", 2: "已确认", 3: "处理中", 4: "已关闭"}
|
||||
AddOperationLog(db, ticketid, operatorid, "status", "状态变更为: "+statusText[status])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddOperationLog(db *gorm.DB, ticketid, operatorid int, action, detail string) error {
|
||||
log := &model.TicketOperationLog{
|
||||
Ticketid: ticketid,
|
||||
Operatorid: operatorid,
|
||||
Action: action,
|
||||
Detail: detail,
|
||||
Createtime: time.Now(),
|
||||
}
|
||||
return db.Create(log).Error
|
||||
}
|
||||
|
||||
func GetOperationLogs(db *gorm.DB, ticketid int) ([]model.TicketOperationLog, error) {
|
||||
var logs []model.TicketOperationLog
|
||||
err := db.Where("ticketid = ?", ticketid).Order("createtime ASC").Find(&logs).Error
|
||||
return logs, err
|
||||
}
|
||||
89
backend/main.go
Normal file
89
backend/main.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/casehub/ticket-workbench/internal/config"
|
||||
"github.com/casehub/ticket-workbench/internal/handler"
|
||||
"github.com/casehub/ticket-workbench/internal/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, err := config.Load("config.yaml")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load config: %v", err)
|
||||
}
|
||||
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
cfg.Database.User,
|
||||
cfg.Database.Password,
|
||||
cfg.Database.Host,
|
||||
cfg.Database.Port,
|
||||
cfg.Database.DBName,
|
||||
)
|
||||
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect database: %v", err)
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get database instance: %v", err)
|
||||
}
|
||||
sqlDB.SetMaxIdleConns(10)
|
||||
sqlDB.SetMaxOpenConns(100)
|
||||
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
r := gin.Default()
|
||||
|
||||
r.Use(middleware.CORS())
|
||||
|
||||
authMiddleware := middleware.Auth(db)
|
||||
|
||||
api := r.Group("/api")
|
||||
{
|
||||
auth := api.Group("/auth")
|
||||
{
|
||||
auth.POST("/login", handler.Login(db))
|
||||
auth.POST("/logout", handler.Logout())
|
||||
auth.GET("/me", authMiddleware, handler.UserInfo())
|
||||
}
|
||||
|
||||
tickets := api.Group("/tickets")
|
||||
tickets.Use(authMiddleware)
|
||||
{
|
||||
tickets.GET("", handler.ListTickets(db))
|
||||
tickets.POST("", handler.CreateTicket(db))
|
||||
tickets.GET("/:id", handler.GetTicket(db))
|
||||
tickets.PUT("/:id", handler.UpdateTicket(db))
|
||||
tickets.PUT("/:id/status", handler.UpdateTicketStatus(db))
|
||||
|
||||
tickets.POST("/:id/analyze", handler.AnalyzeTicket(db, cfg))
|
||||
tickets.GET("/:id/analysis", handler.GetAnalysis(db))
|
||||
tickets.PUT("/:id/analysis", handler.ConfirmAnalysis(db))
|
||||
|
||||
tickets.GET("/:id/notes", handler.ListNotes(db))
|
||||
tickets.POST("/:id/notes", handler.AddNote(db))
|
||||
|
||||
tickets.GET("/:id/logs", handler.GetOperationLogs(db))
|
||||
}
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf(":%d", cfg.Server.Port)
|
||||
if os.Getenv("PORT") != "" {
|
||||
addr = ":" + os.Getenv("PORT")
|
||||
}
|
||||
log.Printf("Server starting on %s", addr)
|
||||
if err := r.Run(addr); err != nil {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user