diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100755 index 0000000..87ebf2a --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "pomdtr.excalidraw-editor", + "editorconfig.editorconfig", + "lokalise.i18n-ally" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 54b8db7..9f15f70 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # good-news -云桌面喜报管理系统 \ No newline at end of file +云桌面喜报管理系统,实现用户上传喜报照片,自动解析照片中的关键内容,比如项目名称、签单办事处、签单点数等。 \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..5fddb40 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1 @@ +./hello.sh \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e4be656 --- /dev/null +++ b/go.mod @@ -0,0 +1,41 @@ +module goodnews + +go 1.20 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.3.0 + github.com/otiai10/gosseract/v2 v2.4.1 + gorm.io/driver/sqlite v1.5.2 + gorm.io/gorm v1.25.2 +) + +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // 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/goccy/go-json v0.10.2 // 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/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // 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.0.8 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e734a37 --- /dev/null +++ b/go.sum @@ -0,0 +1,100 @@ +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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/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/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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/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/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/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +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/otiai10/gosseract/v2 v2.4.1 h1:G8AyBpXEeSlcq8TI85LH/pM5SXk8Djy2GEXisgyblRw= +github.com/otiai10/gosseract/v2 v2.4.1/go.mod h1:1gNWP4Hgr2o7yqWfs6r5bZxAatjOIdqWxJLWsTsembk= +github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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= +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.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +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.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc= +gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= +gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/goodnews.db b/goodnews.db new file mode 100644 index 0000000..dcaa02e Binary files /dev/null and b/goodnews.db differ diff --git a/hello.sh b/hello.sh new file mode 100755 index 0000000..2b2fb28 --- /dev/null +++ b/hello.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +while :; do + { echo -ne "HTTP/1.1 200 OK\r\nContent-Length: $(echo -n "Hello, World!")\r\n\r\nHello, World!"; } | nc -l -p 8080 -q 1 +done diff --git a/main.go b/main.go new file mode 100644 index 0000000..bfce395 --- /dev/null +++ b/main.go @@ -0,0 +1,259 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "github.com/gin-gonic/gin" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "github.com/google/uuid" + "goodnews/models" + "goodnews/services" +) + +// 全局数据库连接 +var db *gorm.DB + +func main() { + // 初始化数据库 + var err error + db, err = gorm.Open(sqlite.Open("goodnews.db"), &gorm.Config{}) + if err != nil { + panic("failed to connect database") + } + + // 自动迁移数据库 + db.AutoMigrate(&models.GoodNews{}) + + // 初始化Gin路由 + r := gin.Default() + + // 设置静态文件服务 + r.Static("/uploads", "./uploads") + // 设置页面路由 + r.StaticFile("/", "./templates/index.html") + r.StaticFile("/manage", "./templates/manage.html") + + // API路由组 + api := r.Group("/api") + { + api.GET("/news", getGoodNewsList) + api.PUT("/news/:id", updateGoodNews) + api.DELETE("/news/:id", deleteGoodNews) + api.POST("/upload", handleUploadAndCreate) + api.GET("/offices", getOfficesList) + } + + // 启动服务 + r.Run(":8080") +} + +// 获取喜报列表 +func getGoodNewsList(c *gin.Context) { + var goodNewsList []models.GoodNews + result := db.Find(&goodNewsList) + if result.Error != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()}) + return + } + + // 处理每条记录,添加图片URL并设置Office字段 + for i := range goodNewsList { + // 将ImagePath转换为可访问的URL + goodNewsList[i].ImageURL = "/" + goodNewsList[i].ImagePath + // 确保Office字段与Representative字段一致 + goodNewsList[i].Office = goodNewsList[i].Representative + } + + c.JSON(http.StatusOK, goodNewsList) +} + +// 获取代表处列表 +func getOfficesList(c *gin.Context) { + var offices []string + result := db.Model(&models.GoodNews{}).Distinct().Pluck("representative", &offices) + if result.Error != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()}) + return + } + c.JSON(http.StatusOK, offices) +} + +// 处理文件上传并创建喜报 +func handleUploadAndCreate(c *gin.Context) { + // 获取上传的图片 + file, err := c.FormFile("file") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "请选择要上传的图片文件"}) + return + } + + // 验证文件类型 + ext := filepath.Ext(file.Filename) + allowedExts := map[string]bool{".jpg": true, ".jpeg": true, ".png": true} + if !allowedExts[strings.ToLower(ext)] { + c.JSON(http.StatusBadRequest, gin.H{"error": "只支持jpg、jpeg和png格式的图片"}) + return + } + + // 验证文件大小(限制为10MB) + if file.Size > 10<<20 { // 10 MB + c.JSON(http.StatusBadRequest, gin.H{"error": "图片大小不能超过10MB"}) + return + } + + // 生成唯一的文件名 + newFileName := uuid.New().String() + ext + uploadDir := filepath.Join(".", "uploads") + filePath := filepath.Join(uploadDir, newFileName) + + // 确保上传目录存在 + if err := os.MkdirAll(uploadDir, 0755); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("创建上传目录失败: %v", err)}) + return + } + + // 保存图片 + if err := c.SaveUploadedFile(file, filePath); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("图片保存失败: %v", err)}) + return + } + + // 创建OCR服务 + ocrService, err := services.NewOCRService() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "OCR服务初始化失败"}) + return + } + defer ocrService.Close() + + // 提取图片信息 + projectName, points, representative, err := ocrService.ExtractInfo(filePath) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("图片识别失败: %v", err)}) + return + } + + fmt.Println("OCR识别信息:", projectName, points, representative) + // 创建喜报记录 + goodNews := models.GoodNews{ + ProjectName: projectName, + Points: points, + Representative: representative, + ImagePath: filePath, + } + + result := db.Create(&goodNews) + if result.Error != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()}) + return + } + + // 重新获取记录以确保包含ID + db.First(&goodNews, goodNews.ID) + goodNews.ImageURL = "/" + goodNews.ImagePath + + c.JSON(http.StatusOK, goodNews) +} + +// 更新喜报 +func updateGoodNews(c *gin.Context) { + var goodNews models.GoodNews + if err := db.First(&goodNews, c.Param("id")).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "记录不存在"}) + return + } + + if err := c.ShouldBindJSON(&goodNews); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + db.Save(&goodNews) + c.JSON(http.StatusOK, goodNews) +} + +// 删除喜报 +func deleteGoodNews(c *gin.Context) { + var goodNews models.GoodNews + if err := db.First(&goodNews, c.Param("id")).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "记录不存在"}) + return + } + + db.Delete(&goodNews) + c.JSON(http.StatusOK, gin.H{"message": "删除成功"}) +} + +// 处理文件上传 +func handleFileUpload(c *gin.Context) { + // 获取上传的文件 + file, err := c.FormFile("file") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "请选择要上传的文件"}) + return + } + + // 验证文件类型 + ext := filepath.Ext(file.Filename) + allowedExts := map[string]bool{".jpg": true, ".jpeg": true, ".png": true} + if !allowedExts[strings.ToLower(ext)] { + c.JSON(http.StatusBadRequest, gin.H{"error": "只支持jpg、jpeg和png格式的图片"}) + return + } + + // 生成唯一的文件名 + newFileName := uuid.New().String() + ext + uploadDir := filepath.Join(".", "uploads") + filePath := filepath.Join(uploadDir, newFileName) + + // 确保上传目录存在 + if err := os.MkdirAll(uploadDir, 0755); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("创建上传目录失败: %v", err)}) + return + } + + // 保存图片 + if err := c.SaveUploadedFile(file, filePath); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("图片保存失败: %v", err)}) + return + } + + // 创建OCR服务 + ocrService, err := services.NewOCRService() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "OCR服务初始化失败"}) + return + } + defer ocrService.Close() + + // 提取图片信息 + projectName, points, representative, err := ocrService.ExtractInfo(filePath) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("图片识别失败: %v", err)}) + return + } + + // 创建喜报记录 + goodNews := models.GoodNews{ + ProjectName: projectName, + Points: points, + Representative: representative, + ImagePath: filePath, + } + + result := db.Create(&goodNews) + if result.Error != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()}) + return + } + + // 重新获取记录以确保包含ID + db.First(&goodNews, goodNews.ID) + goodNews.ImageURL = "/" + goodNews.ImagePath + + c.JSON(http.StatusOK, goodNews) +} \ No newline at end of file diff --git a/models/goodnews.go b/models/goodnews.go new file mode 100644 index 0000000..a283554 --- /dev/null +++ b/models/goodnews.go @@ -0,0 +1,19 @@ +package models + +import ( + "time" + "gorm.io/gorm" +) + +// GoodNews 喜报数据模型 +type GoodNews struct { + gorm.Model + ID uint `json:"id" gorm:"primarykey"` + ProjectName string `json:"project_name"` + Points int `json:"points"` + Representative string `json:"representative"` + ImagePath string `json:"image_path"` + Office string `json:"office"` + UploadTime time.Time `json:"upload_time" gorm:"autoCreateTime"` + ImageURL string `json:"image_url" gorm:"-"` +} \ No newline at end of file diff --git a/project.md b/project.md new file mode 100644 index 0000000..d866fae --- /dev/null +++ b/project.md @@ -0,0 +1,15 @@ +# 功能说明 +## 页面规划 +本项目有两个页面 ++ 首页展示数据库中已有的喜报图片,采用流式布局,每行3个,自动适配手机。 ++ 瀑布流展示数据库中的喜报图片,默认按时间倒序展示,可以按月份、所属代表处进行过滤。 ++ 瀑布流展示数据库中的喜报图片,点击图片后可查看原图。 ++ 管理页面以列表形式展现数据库记录,数据库字段包括:id,项目名称,下单点数,所属代表处,上传时间,图片位置等。 提供修改和删除功能。 ++ 管理页面上提供上传按钮,上传喜报图片,服务端解析图片后自动生成新一条的数据库记录。 + +# 技术栈 ++ 本项目后端采用golang+gin ++ 前端采用element-ui ++ 数据库采用sqlite ++ 图片存储采用七牛云 ++ 部署采用docker \ No newline at end of file diff --git a/services/ocr_service.go b/services/ocr_service.go new file mode 100644 index 0000000..90e2a2d --- /dev/null +++ b/services/ocr_service.go @@ -0,0 +1,88 @@ +package services + +import ( + "github.com/otiai10/gosseract/v2" + "regexp" + "strconv" + "strings" +) + +// OCRService 提供OCR文字识别服务 +type OCRService struct { + client *gosseract.Client +} + +// NewOCRService 创建新的OCR服务实例 +func NewOCRService() (*OCRService, error) { + client := gosseract.NewClient() + return &OCRService{client: client}, nil +} + +// Close 关闭OCR服务 +func (s *OCRService) Close() { + s.client.Close() +} + +// ExtractInfo 从图片中提取喜报信息 +func (s *OCRService) ExtractInfo(imagePath string) (string, int, string, error) { + // 设置中文语言包 + err := s.client.SetLanguage("chi_sim") + if err != nil { + return "", 0, "", err + } + + // 设置图片 + err = s.client.SetImage(imagePath) + if err != nil { + return "", 0, "", err + } + + // 获取文本 + text, err := s.client.Text() + if err != nil { + return "", 0, "", err + } + + // 提取项目名称 + projectName := extractProjectName(text) + + // 提取点数 + points := extractPoints(text) + + // 提取代表处 + representative := extractRepresentative(text) + + return projectName, points, representative, nil +} + +// 提取项目名称 +func extractProjectName(text string) string { + lines := strings.Split(text, "\n") + for _, line := range lines { + if strings.Contains(line, "项目") { + return strings.TrimSpace(line) + } + } + return "" +} + +// 提取点数 +func extractPoints(text string) int { + re := regexp.MustCompile(`(\d+)\s*点|points?`) + matches := re.FindStringSubmatch(text) + if len(matches) > 1 { + points, _ := strconv.Atoi(matches[1]) + return points + } + return 0 +} + +// 提取代表处 +func extractRepresentative(text string) string { + re := regexp.MustCompile(`([^\s]+代表处)`) + matches := re.FindStringSubmatch(text) + if len(matches) > 1 { + return matches[1] + } + return "" +} \ No newline at end of file diff --git a/services/ocr_service_test.go b/services/ocr_service_test.go new file mode 100644 index 0000000..556aa24 --- /dev/null +++ b/services/ocr_service_test.go @@ -0,0 +1,54 @@ +package services + +import ( + "testing" +) + +func TestOCRService_ExtractInfo(t *testing.T) { + // 创建OCR服务实例 + ocr, err := NewOCRService() + if err != nil { + t.Fatalf("Failed to create OCR service: %v", err) + } + defer ocr.Close() + + // 测试用例 + tests := []struct { + name string + imagePath string + wantProject string + wantPoints int + wantRep string + wantErr bool + }{ + { + name: "test_image", + imagePath: "../uploads/test_image.jpg", + // 预期结果将根据实际测试图片调整 + wantProject: "乌省旗信创云桌面项目", + wantPoints: 360, + wantRep: "内蒙代表处", + wantErr: false, + }, + } + + // 运行测试用例 + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + project, points, rep, err := ocr.ExtractInfo(tt.imagePath) + if (err != nil) != tt.wantErr { + t.Errorf("ExtractInfo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if project != tt.wantProject { + t.Errorf("ExtractInfo() project = %v, want %v", project, tt.wantProject) + } + if points != tt.wantPoints { + t.Errorf("ExtractInfo() points = %v, want %v", points, tt.wantPoints) + } + if rep != tt.wantRep { + t.Errorf("ExtractInfo() representative = %v, want %v", rep, tt.wantRep) + } + }) + } +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..3871d4b --- /dev/null +++ b/templates/index.html @@ -0,0 +1,284 @@ + + + + + + 喜报管理系统 + + + + + + +
+ + +
+
+ + +

喜报管理系统

+
+ + 管理喜报 + +
+
+ +
+ + + + + + + + + + + + + 重置筛选 + + +
+ +
+
+ + +
+

{{ item.project_name }}

+

签单点数: {{ item.points }}

+

代表处: {{ item.office }}

+

上传时间: {{ formatDate(item.upload_time) }}

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/templates/manage.html b/templates/manage.html new file mode 100644 index 0000000..7a931af --- /dev/null +++ b/templates/manage.html @@ -0,0 +1,343 @@ + + + + + + 喜报管理系统 - 管理页面 + + + + + + + +
+ + +
+
+ + +

喜报管理系统 - 管理页面

+
+ + 返回首页 + 上传喜报 + +
+
+ + + + + + + + + + + + + + + + + + + +
拖拽文件到此处或 点击上传
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/uploads/.gitkeep b/uploads/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/uploads/0c1e39fa-7496-408c-9509-06447c38e245.png b/uploads/0c1e39fa-7496-408c-9509-06447c38e245.png new file mode 100644 index 0000000..5d5027b Binary files /dev/null and b/uploads/0c1e39fa-7496-408c-9509-06447c38e245.png differ diff --git a/uploads/0d131e39-f2dd-4e83-87f4-f17e807b5594.png b/uploads/0d131e39-f2dd-4e83-87f4-f17e807b5594.png new file mode 100644 index 0000000..d9341e9 Binary files /dev/null and b/uploads/0d131e39-f2dd-4e83-87f4-f17e807b5594.png differ diff --git a/uploads/1c1c6917-4a27-472d-b430-804a41dfacb3.png b/uploads/1c1c6917-4a27-472d-b430-804a41dfacb3.png new file mode 100644 index 0000000..d9341e9 Binary files /dev/null and b/uploads/1c1c6917-4a27-472d-b430-804a41dfacb3.png differ diff --git a/uploads/233e36d1-2d26-464a-b724-cc691c1eaa8e.png b/uploads/233e36d1-2d26-464a-b724-cc691c1eaa8e.png new file mode 100644 index 0000000..8c4aeee Binary files /dev/null and b/uploads/233e36d1-2d26-464a-b724-cc691c1eaa8e.png differ diff --git a/uploads/304cc7cf-db0c-4ed8-916b-d67e2b5ec969.png b/uploads/304cc7cf-db0c-4ed8-916b-d67e2b5ec969.png new file mode 100644 index 0000000..7194e7a Binary files /dev/null and b/uploads/304cc7cf-db0c-4ed8-916b-d67e2b5ec969.png differ diff --git a/uploads/39e2b62b-54dd-4089-80a0-8bf938866fa3.png b/uploads/39e2b62b-54dd-4089-80a0-8bf938866fa3.png new file mode 100644 index 0000000..7194e7a Binary files /dev/null and b/uploads/39e2b62b-54dd-4089-80a0-8bf938866fa3.png differ diff --git a/uploads/486bd310-8643-4bb6-9734-a6bf03dd6d32.png b/uploads/486bd310-8643-4bb6-9734-a6bf03dd6d32.png new file mode 100644 index 0000000..8c4aeee Binary files /dev/null and b/uploads/486bd310-8643-4bb6-9734-a6bf03dd6d32.png differ diff --git a/uploads/5f2e5571-193a-491b-9b9b-eb9af6800699.png b/uploads/5f2e5571-193a-491b-9b9b-eb9af6800699.png new file mode 100644 index 0000000..5d5027b Binary files /dev/null and b/uploads/5f2e5571-193a-491b-9b9b-eb9af6800699.png differ diff --git a/uploads/83ce6b2a-acd4-4fd1-97dd-68b0bd659968.png b/uploads/83ce6b2a-acd4-4fd1-97dd-68b0bd659968.png new file mode 100644 index 0000000..5d5027b Binary files /dev/null and b/uploads/83ce6b2a-acd4-4fd1-97dd-68b0bd659968.png differ diff --git a/uploads/8a6f9424-48ec-46e9-8a89-70744f777d4c.png b/uploads/8a6f9424-48ec-46e9-8a89-70744f777d4c.png new file mode 100644 index 0000000..d9341e9 Binary files /dev/null and b/uploads/8a6f9424-48ec-46e9-8a89-70744f777d4c.png differ diff --git a/uploads/9d3c710b-c11e-4149-95fd-eed0a1ba99da.png b/uploads/9d3c710b-c11e-4149-95fd-eed0a1ba99da.png new file mode 100644 index 0000000..8c4aeee Binary files /dev/null and b/uploads/9d3c710b-c11e-4149-95fd-eed0a1ba99da.png differ diff --git a/uploads/c1087998-e2b7-4bde-be06-4cdec46623c3.png b/uploads/c1087998-e2b7-4bde-be06-4cdec46623c3.png new file mode 100644 index 0000000..5d5027b Binary files /dev/null and b/uploads/c1087998-e2b7-4bde-be06-4cdec46623c3.png differ diff --git a/uploads/d67bc5d3-ee53-48b1-90ef-8c3fe04a97fd.png b/uploads/d67bc5d3-ee53-48b1-90ef-8c3fe04a97fd.png new file mode 100644 index 0000000..7194e7a Binary files /dev/null and b/uploads/d67bc5d3-ee53-48b1-90ef-8c3fe04a97fd.png differ diff --git a/uploads/e1829c12-1021-4b95-8311-2c4616f499f1.png b/uploads/e1829c12-1021-4b95-8311-2c4616f499f1.png new file mode 100644 index 0000000..7194e7a Binary files /dev/null and b/uploads/e1829c12-1021-4b95-8311-2c4616f499f1.png differ