diff --git a/cmd/webserver.go b/cmd/webserver.go index 56840bbf..b9f1a85a 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -3,7 +3,10 @@ package cmd import ( "fmt" "path/filepath" + "time" + "github.com/gin-contrib/cache" + "github.com/gin-contrib/cache/persistence" "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" @@ -165,6 +168,13 @@ func startWebServer(c *cli.Context) error { } } + qrCodeRoute := router.Group("/qrcode") + qrCodeRoute.Use(bindMiddleware(middlewares.RequestId(config))) + { + qrCodeCacheStore := persistence.NewInMemoryStore(time.Minute) + qrCodeRoute.GET("/mobile_url.png", bindCachedPngImage(api.QrCodes.MobileUrlQrCodeHandler, qrCodeCacheStore)) + } + apiRoute := router.Group("/api") apiRoute.Use(bindMiddleware(middlewares.RequestId(config))) @@ -334,6 +344,19 @@ func bindCsv(fn core.DataHandlerFunc) gin.HandlerFunc { } } +func bindCachedPngImage(fn core.DataHandlerFunc, store persistence.CacheStore) gin.HandlerFunc { + return cache.CachePage(store, time.Minute, func(ginCtx *gin.Context) { + c := core.WrapContext(ginCtx) + result, _, err := fn(c) + + if err != nil { + utils.PrintDataErrorResult(c, "text/text", err) + } else { + utils.PrintDataSuccessResult(c, "img/png", "", result) + } + }) +} + func bindProxy(fn core.ProxyHandlerFunc) gin.HandlerFunc { return func(ginCtx *gin.Context) { c := core.WrapContext(ginCtx) diff --git a/go.mod b/go.mod index 2a9ef7b6..1c3c0841 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/mayswind/ezbookkeeping go 1.20 require ( + github.com/boombuler/barcode v1.0.1 + github.com/gin-contrib/cache v1.2.0 github.com/gin-contrib/gzip v0.0.6 github.com/gin-gonic/gin v1.9.0 github.com/go-playground/validator/v10 v10.14.0 @@ -20,7 +22,7 @@ require ( ) require ( - github.com/boombuler/barcode v1.0.1 // indirect + github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect github.com/bytedance/sonic v1.8.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect @@ -31,14 +33,17 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/goccy/go-json v0.10.0 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/gomodule/redigo v1.8.9 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mattn/go-isatty v0.0.17 // indirect + github.com/memcachier/mc/v3 v3.0.3 // 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.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect diff --git a/go.sum b/go.sum index 45e85d26..7e9b0323 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw= +github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= @@ -75,6 +77,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo 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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/cache v1.2.0 h1:WA+AJR4kmHDTaLLShCHo/IeWVmmGRZ3Lsr3JQ46tFlE= +github.com/gin-contrib/cache v1.2.0/go.mod h1:2KkFL8PSnPF3Tt5E2Jpc3HWuBAUKqGZnClCFMm0tXQI= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -128,6 +132,8 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= +github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -274,6 +280,8 @@ github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/memcachier/mc/v3 v3.0.3 h1:qii+lDiPKi36O4Xg+HVKwHu6Oq+Gt17b+uEiA0Drwv4= +github.com/memcachier/mc/v3 v3.0.3/go.mod h1:GzjocBahcXPxt2cmqzknrgqCOmMxiSzhVKPOe90Tpug= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -348,6 +356,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 h1:pyecQtsPmlkCsMkYhT5iZ+sUXuwee+OvfuJjinEA3ko= +github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62/go.mod h1:65XQgovT59RWatovFwnwocoUxiI/eENTnOY5GK3STuY= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= diff --git a/pkg/api/qrcodes.go b/pkg/api/qrcodes.go new file mode 100644 index 00000000..3811ea5e --- /dev/null +++ b/pkg/api/qrcodes.go @@ -0,0 +1,51 @@ +package api + +import ( + "bytes" + "image/png" + + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/qr" + + "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/errs" + "github.com/mayswind/ezbookkeeping/pkg/settings" +) + +const ( + qrCodeDefaultWidth int = 320 + qrCodeDefaultHeight int = 320 +) + +// QrCodesApi represents qrcode generator api +type QrCodesApi struct { +} + +// Initialize a qrcode generator api singleton instance +var ( + QrCodes = &QrCodesApi{} +) + +// MobileUrlQrCodeHandler returns a mobile url qr code image +func (a *QrCodesApi) MobileUrlQrCodeHandler(c *core.Context) ([]byte, string, *errs.Error) { + fullUrl := settings.Container.Current.RootUrl + "mobile" + data, err := a.generateUrlQrCode(c, fullUrl) + + if err != nil { + return nil, "", errs.ErrOperationFailed + } + + return data, "", nil +} + +func (a *QrCodesApi) generateUrlQrCode(c *core.Context, url string) ([]byte, *errs.Error) { + qrCodeImg, _ := qr.Encode(url, qr.M, qr.Auto) + qrCodeImg, _ = barcode.Scale(qrCodeImg, qrCodeDefaultWidth, qrCodeDefaultHeight) + imgData := &bytes.Buffer{} + + if err := png.Encode(imgData, qrCodeImg); err != nil { + return nil, errs.ErrOperationFailed + } + + return imgData.Bytes(), nil +} diff --git a/src/locales/en.js b/src/locales/en.js index 9a1be196..a7e1bd99 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -1135,6 +1135,8 @@ export default { 'Exchange rates data has been updated': 'Exchange rates data has been updated', 'Exchange rates data is up to date': 'Exchange rates data is up to date', 'Unable to get exchange rates data': 'Unable to get exchange rates data', + 'Use on Mobile Device': 'Use on Mobile Device', + 'You can scan the below QR code on your mobile device.': 'You can scan the below QR code on your mobile device.', 'Switch to Mobile Version': 'Switch to Mobile Version', 'Switch to Desktop Version': 'Switch to Desktop Version', 'About': 'About', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index 171562f2..bc1c83e6 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -1135,6 +1135,8 @@ export default { 'Exchange rates data has been updated': '汇率数据已更新', 'Exchange rates data is up to date': '汇率数据已是最新', 'Unable to get exchange rates data': '无法获取汇率数据', + 'Use on Mobile Device': '在移动设备使用', + 'You can scan the below QR code on your mobile device.': '您可以在您的移动设备上扫描下方二维码。', 'Switch to Mobile Version': '切换到移动版', 'Switch to Desktop Version': '切换到桌面版', 'About': '关于', diff --git a/src/views/desktop/MainLayout.vue b/src/views/desktop/MainLayout.vue index dce955ee..442b08a7 100644 --- a/src/views/desktop/MainLayout.vue +++ b/src/views/desktop/MainLayout.vue @@ -73,8 +73,14 @@
{{ $t('You can scan the below QR code on your mobile device.') }}
+