feat: 初始化核心应用结构,新增循环交易、短信、分配规则和用户设置等服务,并更新相关依赖和配置。
This commit is contained in:
8
.env.dev
8
.env.dev
@@ -93,3 +93,11 @@ IMAGE_UPLOAD_DIR=./uploads/images
|
||||
MAX_IMAGE_SIZE=10485760
|
||||
ALLOWED_IMAGE_TYPES=image/jpeg,image/png,image/heic
|
||||
MAX_IMAGES_PER_TX=9
|
||||
|
||||
# ============================================
|
||||
# Aliyun SMS 配置
|
||||
# ============================================
|
||||
ALIYUN_ACCESS_KEY_ID=
|
||||
ALIYUN_ACCESS_KEY_SECRET=
|
||||
ALIYUN_SIGN_NAME=
|
||||
ALIYUN_TEMPLATE_CODE=
|
||||
|
||||
@@ -100,3 +100,11 @@ IMAGE_UPLOAD_DIR=./uploads/images
|
||||
MAX_IMAGE_SIZE=10485760
|
||||
ALLOWED_IMAGE_TYPES=image/jpeg,image/png,image/heic
|
||||
MAX_IMAGES_PER_TX=9
|
||||
|
||||
# ============================================
|
||||
# Aliyun SMS 配置
|
||||
# ============================================
|
||||
ALIYUN_ACCESS_KEY_ID=
|
||||
ALIYUN_ACCESS_KEY_SECRET=
|
||||
ALIYUN_SIGN_NAME=
|
||||
ALIYUN_TEMPLATE_CODE=
|
||||
4
go.mod
4
go.mod
@@ -18,6 +18,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 // indirect
|
||||
github.com/bytedance/sonic v1.10.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
@@ -32,12 +33,14 @@ require (
|
||||
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/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
@@ -52,5 +55,6 @@ require (
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
59
go.sum
59
go.sum
@@ -1,3 +1,9 @@
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 h1:qagvUyrgOnBIlVRQWOyCZGVKUIYbMBdGdJ104vBpRFU=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
@@ -16,17 +22,20 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpV
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
||||
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
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=
|
||||
@@ -39,8 +48,11 @@ github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ
|
||||
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/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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=
|
||||
@@ -48,17 +60,24 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
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/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
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/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
|
||||
github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
@@ -70,10 +89,14 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
|
||||
@@ -101,6 +124,8 @@ github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sit
|
||||
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
||||
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/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||
@@ -109,16 +134,35 @@ github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstf
|
||||
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=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
|
||||
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
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.0.0-20190910094157-69e4b8554b2a/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/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
@@ -126,13 +170,28 @@ golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
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/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/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.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
|
||||
@@ -67,6 +67,12 @@ type Config struct {
|
||||
// Task Queue configuration
|
||||
MQWorkerCount int
|
||||
MQPollInterval time.Duration
|
||||
|
||||
// Aliyun SMS configuration
|
||||
AliyunAccessKeyID string
|
||||
AliyunAccessKeySecret string
|
||||
AliyunSignName string
|
||||
AliyunTemplateCode string
|
||||
}
|
||||
|
||||
// Load loads configuration from environment variables
|
||||
@@ -131,6 +137,12 @@ func Load() *Config {
|
||||
// Task Queue
|
||||
MQWorkerCount: getEnvInt("MQ_WORKER_COUNT", 2),
|
||||
MQPollInterval: getEnvDuration("MQ_POLL_INTERVAL", 5*time.Second),
|
||||
|
||||
// Aliyun SMS
|
||||
AliyunAccessKeyID: getEnv("ALIYUN_ACCESS_KEY_ID", ""),
|
||||
AliyunAccessKeySecret: getEnv("ALIYUN_ACCESS_KEY_SECRET", ""),
|
||||
AliyunSignName: getEnv("ALIYUN_SIGN_NAME", ""),
|
||||
AliyunTemplateCode: getEnv("ALIYUN_TEMPLATE_CODE", ""),
|
||||
}
|
||||
|
||||
// Ensure data directory exists
|
||||
|
||||
@@ -513,6 +513,7 @@ type AllocationRule struct {
|
||||
TriggerType TriggerType `gorm:"size:20;not null" json:"trigger_type"`
|
||||
SourceAccountID *uint `gorm:"index" json:"source_account_id,omitempty"` // 触发分配的源账户,为空则匹配所有账户
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
AutoExecute bool `gorm:"default:true" json:"auto_execute"` // 是否自动执行(不需要确认)
|
||||
|
||||
// Relationships
|
||||
SourceAccount *Account `gorm:"foreignKey:SourceAccountID" json:"source_account,omitempty"`
|
||||
|
||||
@@ -8,14 +8,14 @@ import (
|
||||
// Feature: accounting-feature-upgrade
|
||||
// Validates: Requirements 5.4, 6.5, 8.25-8.27
|
||||
type UserSettings struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
UserID *uint `gorm:"uniqueIndex" json:"user_id,omitempty"`
|
||||
PreciseTimeEnabled bool `gorm:"default:true" json:"precise_time_enabled"`
|
||||
IconLayout string `gorm:"size:10;default:'five'" json:"icon_layout"` // four, five, six
|
||||
ImageCompression string `gorm:"size:10;default:'medium'" json:"image_compression"` // low, medium, high
|
||||
ShowReimbursementBtn bool `gorm:"default:true" json:"show_reimbursement_btn"`
|
||||
ShowRefundBtn bool `gorm:"default:true" json:"show_refund_btn"`
|
||||
CurrentLedgerID *uint `gorm:"index" json:"current_ledger_id,omitempty"`
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
UserID *uint `gorm:"uniqueIndex" json:"user_id,omitempty"`
|
||||
PreciseTimeEnabled bool `gorm:"default:true" json:"precise_time_enabled"`
|
||||
IconLayout string `gorm:"size:10;default:'five'" json:"icon_layout"` // four, five, six
|
||||
ImageCompression string `gorm:"size:10;default:'medium'" json:"image_compression"` // low, medium, high
|
||||
ShowReimbursementBtn bool `gorm:"default:true" json:"show_reimbursement_btn"`
|
||||
ShowRefundBtn bool `gorm:"default:true" json:"show_refund_btn"`
|
||||
CurrentLedgerID *uint `gorm:"index" json:"current_ledger_id,omitempty"`
|
||||
|
||||
// Default account settings
|
||||
// Feature: financial-core-upgrade
|
||||
@@ -23,6 +23,8 @@ type UserSettings struct {
|
||||
DefaultExpenseAccountID *uint `gorm:"index" json:"default_expense_account_id,omitempty"`
|
||||
DefaultIncomeAccountID *uint `gorm:"index" json:"default_income_account_id,omitempty"`
|
||||
|
||||
Phone string `gorm:"size:20" json:"phone,omitempty"` // For SMS notifications
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func Setup(db *gorm.DB, yunAPIClient *service.YunAPIClient, cfg *config.Config)
|
||||
classificationService := service.NewClassificationService(classificationRepo, categoryRepo)
|
||||
transactionService := service.NewTransactionService(transactionRepo, accountRepo, categoryRepo, tagRepo, ledgerRepo, db)
|
||||
imageService := service.NewImageService(transactionImageRepo, transactionRepo, db, cfg.ImageUploadDir)
|
||||
recurringService := service.NewRecurringTransactionService(recurringRepo, transactionRepo, accountRepo, categoryRepo, allocationRuleRepo, allocationRecordRepo, piggyBankRepo, db)
|
||||
recurringService := service.NewRecurringTransactionService(recurringRepo, transactionRepo, accountRepo, categoryRepo, allocationRuleRepo, allocationRecordRepo, piggyBankRepo, userSettingsRepo, db)
|
||||
exchangeRateService := service.NewExchangeRateService(exchangeRateRepo)
|
||||
reportService := service.NewReportService(reportRepo, exchangeRateRepo)
|
||||
pdfExportService := service.NewPDFExportService(reportRepo, transactionRepo, exchangeRateRepo)
|
||||
@@ -355,7 +355,7 @@ func SetupWithRedis(db *gorm.DB, yunAPIClient *service.YunAPIClient, redisClient
|
||||
classificationService := service.NewClassificationService(classificationRepo, categoryRepo)
|
||||
transactionService := service.NewTransactionService(transactionRepo, accountRepo, categoryRepo, tagRepo, ledgerRepo, db)
|
||||
imageService := service.NewImageService(transactionImageRepo, transactionRepo, db, cfg.ImageUploadDir)
|
||||
recurringService := service.NewRecurringTransactionService(recurringRepo, transactionRepo, accountRepo, categoryRepo, allocationRuleRepo, allocationRecordRepo, piggyBankRepo, db)
|
||||
recurringService := service.NewRecurringTransactionService(recurringRepo, transactionRepo, accountRepo, categoryRepo, allocationRuleRepo, allocationRecordRepo, piggyBankRepo, userSettingsRepo, db)
|
||||
reportService := service.NewReportService(reportRepo, exchangeRateRepo)
|
||||
pdfExportService := service.NewPDFExportService(reportRepo, transactionRepo, exchangeRateRepo)
|
||||
excelExportService := service.NewExcelExportService(reportRepo, transactionRepo, exchangeRateRepo)
|
||||
|
||||
@@ -31,6 +31,7 @@ type AllocationRuleInput struct {
|
||||
TriggerType models.TriggerType `json:"trigger_type" binding:"required"`
|
||||
SourceAccountID *uint `json:"source_account_id,omitempty"` // 触发分配的源账户
|
||||
IsActive bool `json:"is_active"`
|
||||
AutoExecute bool `json:"auto_execute"`
|
||||
Targets []AllocationTargetInput `json:"targets" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
@@ -50,6 +51,7 @@ type AllocationResult struct {
|
||||
AllocatedAmount float64 `json:"allocated_amount"`
|
||||
Remaining float64 `json:"remaining"`
|
||||
Allocations []AllocationDetail `json:"allocations"`
|
||||
AutoExecuted bool `json:"auto_executed"`
|
||||
}
|
||||
|
||||
// AllocationDetail 单个分配目标的详情
|
||||
@@ -114,6 +116,7 @@ func (s *AllocationRuleService) CreateAllocationRule(input AllocationRuleInput)
|
||||
TriggerType: input.TriggerType,
|
||||
SourceAccountID: input.SourceAccountID,
|
||||
IsActive: input.IsActive,
|
||||
AutoExecute: input.AutoExecute,
|
||||
}
|
||||
|
||||
// 开始数据库事务
|
||||
@@ -227,6 +230,7 @@ func (s *AllocationRuleService) UpdateAllocationRule(userID, id uint, input Allo
|
||||
rule.TriggerType = input.TriggerType
|
||||
rule.SourceAccountID = input.SourceAccountID
|
||||
rule.IsActive = input.IsActive
|
||||
rule.AutoExecute = input.AutoExecute
|
||||
|
||||
// 保存规则
|
||||
if err := tx.Save(rule).Error; err != nil {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RecurringTransactionService handles business logic for recurring transactions
|
||||
// RecurringTransactionService handles business logic for recurring transactions
|
||||
type RecurringTransactionService struct {
|
||||
recurringRepo *repository.RecurringTransactionRepository
|
||||
@@ -20,6 +21,8 @@ type RecurringTransactionService struct {
|
||||
allocationRuleRepo *repository.AllocationRuleRepository
|
||||
recordRepo *repository.AllocationRecordRepository
|
||||
piggyBankRepo *repository.PiggyBankRepository
|
||||
userSettingsRepo *repository.UserSettingsRepository
|
||||
smsService *SmsService
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
@@ -32,6 +35,8 @@ func NewRecurringTransactionService(
|
||||
allocationRuleRepo *repository.AllocationRuleRepository,
|
||||
recordRepo *repository.AllocationRecordRepository,
|
||||
piggyBankRepo *repository.PiggyBankRepository,
|
||||
userSettingsRepo *repository.UserSettingsRepository,
|
||||
smsService *SmsService,
|
||||
db *gorm.DB,
|
||||
) *RecurringTransactionService {
|
||||
return &RecurringTransactionService{
|
||||
@@ -42,6 +47,8 @@ func NewRecurringTransactionService(
|
||||
allocationRuleRepo: allocationRuleRepo,
|
||||
recordRepo: recordRepo,
|
||||
piggyBankRepo: piggyBankRepo,
|
||||
userSettingsRepo: userSettingsRepo,
|
||||
smsService: smsService,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
@@ -377,6 +384,27 @@ func (s *RecurringTransactionService) ProcessDueTransactions(userID uint, now ti
|
||||
result.Transactions = append(result.Transactions, transaction)
|
||||
}
|
||||
|
||||
// Send SMS notifications for auto-executed allocations
|
||||
for _, allocation := range result.Allocations {
|
||||
if allocation.AutoExecuted {
|
||||
// Get user phone number
|
||||
userSettings, err := s.userSettingsRepo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
// Log error but don't fail the request
|
||||
fmt.Printf("Failed to get user settings for SMS: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if userSettings != nil && userSettings.Phone != "" && s.smsService != nil {
|
||||
go func(phone string, amount float64, ruleName string) {
|
||||
if err := s.smsService.SendAllocationNotification(phone, amount, ruleName); err != nil {
|
||||
fmt.Printf("Failed to send allocation SMS: %v\n", err)
|
||||
}
|
||||
}(userSettings.Phone, allocation.TotalAmount, allocation.RuleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -429,7 +457,7 @@ func (s *RecurringTransactionService) applyAllocationRulesForIncome(userID uint,
|
||||
continue
|
||||
}
|
||||
|
||||
// Get target name and apply allocation
|
||||
// Get target name
|
||||
targetName := ""
|
||||
|
||||
switch target.TargetType {
|
||||
@@ -440,20 +468,23 @@ func (s *RecurringTransactionService) applyAllocationRulesForIncome(userID uint,
|
||||
}
|
||||
targetName = targetAccount.Name
|
||||
|
||||
// Add to target account
|
||||
targetAccount.Balance += allocatedAmount
|
||||
if err := tx.Save(&targetAccount).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to update target account balance: %w", err)
|
||||
}
|
||||
// Execute transfer only if AutoExecute is true
|
||||
if rule.AutoExecute {
|
||||
// Add to target account
|
||||
targetAccount.Balance += allocatedAmount
|
||||
if err := tx.Save(&targetAccount).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to update target account balance: %w", err)
|
||||
}
|
||||
|
||||
// Deduct from source account
|
||||
var sourceAccount models.Account
|
||||
if err := tx.First(&sourceAccount, accountID).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to get source account: %w", err)
|
||||
}
|
||||
sourceAccount.Balance -= allocatedAmount
|
||||
if err := tx.Save(&sourceAccount).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to update source account balance: %w", err)
|
||||
// Deduct from source account
|
||||
var sourceAccount models.Account
|
||||
if err := tx.First(&sourceAccount, accountID).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to get source account: %w", err)
|
||||
}
|
||||
sourceAccount.Balance -= allocatedAmount
|
||||
if err := tx.Save(&sourceAccount).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to update source account balance: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
case models.TargetTypePiggyBank:
|
||||
@@ -463,20 +494,23 @@ func (s *RecurringTransactionService) applyAllocationRulesForIncome(userID uint,
|
||||
}
|
||||
targetName = piggyBank.Name
|
||||
|
||||
// Add to piggy bank
|
||||
piggyBank.CurrentAmount += allocatedAmount
|
||||
if err := tx.Save(&piggyBank).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to update piggy bank balance: %w", err)
|
||||
}
|
||||
// Execute transfer only if AutoExecute is true
|
||||
if rule.AutoExecute {
|
||||
// Add to piggy bank
|
||||
piggyBank.CurrentAmount += allocatedAmount
|
||||
if err := tx.Save(&piggyBank).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to update piggy bank balance: %w", err)
|
||||
}
|
||||
|
||||
// Deduct from source account
|
||||
var sourceAccount models.Account
|
||||
if err := tx.First(&sourceAccount, accountID).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to get source account: %w", err)
|
||||
}
|
||||
sourceAccount.Balance -= allocatedAmount
|
||||
if err := tx.Save(&sourceAccount).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to update source account balance: %w", err)
|
||||
// Deduct from source account
|
||||
var sourceAccount models.Account
|
||||
if err := tx.First(&sourceAccount, accountID).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to get source account: %w", err)
|
||||
}
|
||||
sourceAccount.Balance -= allocatedAmount
|
||||
if err := tx.Save(&sourceAccount).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to update source account balance: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
74
internal/service/sms_service.go
Normal file
74
internal/service/sms_service.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"accounting-app/internal/config"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi"
|
||||
)
|
||||
|
||||
// SmsService handles SMS notifications
|
||||
type SmsService struct {
|
||||
client *dysmsapi.Client
|
||||
signName string
|
||||
templateCode string
|
||||
enabled bool
|
||||
}
|
||||
|
||||
// NewSmsService creates a new SmsService instance
|
||||
func NewSmsService(cfg *config.Config) *SmsService {
|
||||
if cfg.AliyunAccessKeyID == "" || cfg.AliyunAccessKeySecret == "" {
|
||||
log.Println("Aliyun SMS configuration missing, SMS service disabled")
|
||||
return &SmsService{enabled: false}
|
||||
}
|
||||
|
||||
client, err := dysmsapi.NewClientWithAccessKey("cn-hangzhou", cfg.AliyunAccessKeyID, cfg.AliyunAccessKeySecret)
|
||||
if err != nil {
|
||||
log.Printf("Failed to initialize Aliyun SMS client: %v", err)
|
||||
return &SmsService{enabled: false}
|
||||
}
|
||||
|
||||
return &SmsService{
|
||||
client: client,
|
||||
signName: cfg.AliyunSignName,
|
||||
templateCode: cfg.AliyunTemplateCode,
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
// SendAllocationNotification sends an SMS notification for auto allocation
|
||||
func (s *SmsService) SendAllocationNotification(phoneNumber string, totalAmount float64, ruleName string) error {
|
||||
if !s.enabled || phoneNumber == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
request := dysmsapi.CreateSendSmsRequest()
|
||||
request.Scheme = "https"
|
||||
request.PhoneNumbers = phoneNumber
|
||||
request.SignName = s.signName
|
||||
request.TemplateCode = s.templateCode
|
||||
|
||||
// Template params: {"amount": "1000", "rule": "Salary Allocation"}
|
||||
// You might need to adjust parameters based on your actual Aliyun template
|
||||
params := map[string]string{
|
||||
"amount": fmt.Sprintf("%.2f", totalAmount),
|
||||
"rule": ruleName,
|
||||
}
|
||||
paramBytes, _ := json.Marshal(params)
|
||||
request.TemplateParam = string(paramBytes)
|
||||
|
||||
response, err := s.client.SendSms(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send SMS: %w", err)
|
||||
}
|
||||
|
||||
if response.Code != "OK" {
|
||||
return fmt.Errorf("aliyun SMS error: %s - %s", response.Code, response.Message)
|
||||
}
|
||||
|
||||
log.Printf("SMS sent successfully to %s", phoneNumber)
|
||||
return nil
|
||||
}
|
||||
@@ -36,6 +36,7 @@ type UserSettingsInput struct {
|
||||
ShowReimbursementBtn *bool `json:"show_reimbursement_btn"`
|
||||
ShowRefundBtn *bool `json:"show_refund_btn"`
|
||||
CurrentLedgerID *uint `json:"current_ledger_id"`
|
||||
Phone *string `json:"phone"`
|
||||
}
|
||||
|
||||
// DefaultAccountsInput represents the input data for updating default accounts
|
||||
@@ -148,6 +149,10 @@ func (s *UserSettingsService) UpdateSettings(userID uint, input UserSettingsInpu
|
||||
settings.CurrentLedgerID = input.CurrentLedgerID
|
||||
}
|
||||
|
||||
if input.Phone != nil {
|
||||
settings.Phone = *input.Phone
|
||||
}
|
||||
|
||||
// Save to database
|
||||
if err := s.repo.Update(settings); err != nil {
|
||||
return nil, fmt.Errorf("failed to update settings: %w", err)
|
||||
|
||||
5
migration.sql
Normal file
5
migration.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- Add AutoExecute column to allocation_rules table
|
||||
ALTER TABLE allocation_rules ADD COLUMN auto_execute BOOLEAN DEFAULT TRUE;
|
||||
|
||||
-- Add Phone column to user_settings table
|
||||
ALTER TABLE user_settings ADD COLUMN phone VARCHAR(20);
|
||||
Reference in New Issue
Block a user