feat: initialize golang invite service project

This commit is contained in:
hy001 2026-04-15 19:22:42 +08:00
commit ca828818cf
16 changed files with 4683 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.DS_Store
target/

42
cmd/api/main.go Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"chatapp3-golang/internal/config"
invitehttp "chatapp3-golang/internal/http"
"chatapp3-golang/internal/integration"
"chatapp3-golang/internal/repo"
"chatapp3-golang/internal/service"
"context"
"log"
"time"
)
func main() {
cfg := config.Load()
repository, err := repo.New(cfg)
if err != nil {
log.Fatalf("init repository failed: %v", err)
}
if cfg.AutoMigrate {
if err := repository.AutoMigrate(); err != nil {
log.Fatalf("auto migrate failed: %v", err)
}
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := repository.Ping(ctx); err != nil {
log.Fatalf("ping dependencies failed: %v", err)
}
javaClient := integration.New(cfg)
inviteService := service.NewInviteService(cfg, repository, javaClient)
baishunService := service.NewBaishunService(cfg, repository, javaClient)
router := invitehttp.NewRouter(cfg, repository, javaClient, inviteService, baishunService)
log.Printf("chatapp3-golang invite api listening on %s", cfg.ListenAddr)
if err := router.Run(cfg.ListenAddr); err != nil {
log.Fatalf("run server failed: %v", err)
}
}

45
go.mod Normal file
View File

@ -0,0 +1,45 @@
module chatapp3-golang
go 1.24
require (
github.com/bwmarrin/snowflake v0.3.0
github.com/gin-gonic/gin v1.10.0
github.com/redis/go-redis/v9 v9.7.0
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.25.12
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // 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.20.0 // indirect
github.com/go-sql-driver/mysql v1.7.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.7 // indirect
github.com/leodido/go-urn v1.4.0 // 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/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

112
go.sum Normal file
View File

@ -0,0 +1,112 @@
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
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/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.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
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.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
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/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/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.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
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.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/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/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.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

102
internal/config/config.go Normal file
View File

@ -0,0 +1,102 @@
package config
import (
"os"
"strconv"
"time"
)
type Config struct {
ListenAddr string
PublicBaseURL string
MySQLDSN string
RedisAddr string
RedisPassword string
RedisDB int
AutoMigrate bool
JavaAuthBaseURL string
JavaDeviceBaseURL string
JavaAppBaseURL string
JavaBridgeBaseURL string
JavaOtherBaseURL string
JavaWalletBaseURL string
BaishunPlatformBaseURL string
BaishunAppID int64
BaishunAppChannel string
BaishunAppKey string
BaishunGSP int
BaishunLaunchCodeTTLSeconds int
BaishunSSTokenTTLSeconds int
InternalCallbackSecret string
Timeout time.Duration
}
func Load() Config {
return Config{
ListenAddr: getEnv("INVITE_HTTP_ADDR", ":2900"),
PublicBaseURL: getEnv("INVITE_PUBLIC_BASE_URL", "http://localhost:2900"),
MySQLDSN: getEnv("INVITE_MYSQL_DSN", "root:root@tcp(127.0.0.1:3306)/chatapp3?charset=utf8mb4&parseTime=True&loc=Local"),
RedisAddr: getEnv("INVITE_REDIS_ADDR", "127.0.0.1:6379"),
RedisPassword: getEnv("INVITE_REDIS_PASSWORD", ""),
RedisDB: getEnvInt("INVITE_REDIS_DB", 0),
AutoMigrate: getEnvBool("INVITE_AUTO_MIGRATE", false),
JavaAuthBaseURL: getEnv("INVITE_JAVA_AUTH_BASE_URL", "http://127.0.0.1:2100"),
JavaDeviceBaseURL: getEnv("INVITE_JAVA_DEVICE_BASE_URL", "http://127.0.0.1:2400"),
JavaAppBaseURL: getEnv("INVITE_JAVA_APP_BASE_URL", "http://127.0.0.1:2400"),
JavaBridgeBaseURL: getEnv("INVITE_JAVA_BRIDGE_BASE_URL", "http://127.0.0.1:2200"),
JavaOtherBaseURL: getEnv("GAME_JAVA_OTHER_BASE_URL", getEnv("INVITE_JAVA_APP_BASE_URL", "http://127.0.0.1:2400")),
JavaWalletBaseURL: getEnv("GAME_JAVA_WALLET_BASE_URL", "http://127.0.0.1:2300"),
BaishunPlatformBaseURL: getEnv("BAISHUN_PLATFORM_BASE_URL", ""),
BaishunAppID: getEnvInt64("BAISHUN_APP_ID", 0),
BaishunAppChannel: getEnv("BAISHUN_APP_CHANNEL", ""),
BaishunAppKey: getEnv("BAISHUN_APP_KEY", ""),
BaishunGSP: getEnvInt("BAISHUN_GSP", 101),
BaishunLaunchCodeTTLSeconds: getEnvInt("BAISHUN_LAUNCH_CODE_TTL_SECONDS", 300),
BaishunSSTokenTTLSeconds: getEnvInt("BAISHUN_SS_TOKEN_TTL_SECONDS", 86400),
InternalCallbackSecret: getEnv("GAME_INTERNAL_CALLBACK_SECRET", ""),
Timeout: time.Duration(getEnvInt("INVITE_HTTP_TIMEOUT_SECONDS", 8)) * time.Second,
}
}
func getEnv(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}
func getEnvInt(key string, fallback int) int {
value := os.Getenv(key)
if value == "" {
return fallback
}
parsed, err := strconv.Atoi(value)
if err != nil {
return fallback
}
return parsed
}
func getEnvInt64(key string, fallback int64) int64 {
value := os.Getenv(key)
if value == "" {
return fallback
}
parsed, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return fallback
}
return parsed
}
func getEnvBool(key string, fallback bool) bool {
value := os.Getenv(key)
if value == "" {
return fallback
}
parsed, err := strconv.ParseBool(value)
if err != nil {
return fallback
}
return parsed
}

View File

@ -0,0 +1,277 @@
package http
import (
"chatapp3-golang/internal/config"
"chatapp3-golang/internal/integration"
"chatapp3-golang/internal/service"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
func registerBaishunRoutes(
router *gin.Engine,
cfg config.Config,
javaClient *integration.Client,
baishunService *service.BaishunService,
) {
appGroup := router.Group("/app/game")
appGroup.Use(authMiddleware(javaClient))
appGroup.GET("/room/shortcut", func(c *gin.Context) {
user := mustAuthUser(c)
resp, err := baishunService.ListShortcutGames(c.Request.Context(), user, c.Query("roomId"))
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
appGroup.GET("/room/list", func(c *gin.Context) {
user := mustAuthUser(c)
resp, err := baishunService.ListRoomGames(c.Request.Context(), user, c.Query("roomId"), c.Query("category"))
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
appGroup.GET("/baishun/state", func(c *gin.Context) {
user := mustAuthUser(c)
resp, err := baishunService.GetRoomState(c.Request.Context(), user, c.Query("roomId"))
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
appGroup.POST("/baishun/launch", func(c *gin.Context) {
user := mustAuthUser(c)
var req service.BaishunLaunchAppRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, service.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := baishunService.LaunchGame(c.Request.Context(), user, req, resolveClientIP(c))
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
appGroup.POST("/baishun/close", func(c *gin.Context) {
user := mustAuthUser(c)
var req service.BaishunCloseAppRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, service.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := baishunService.CloseGame(c.Request.Context(), user, req)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
internalGroup := router.Group("/internal/game/baishun")
internalGroup.Use(internalSecretMiddleware(cfg.InternalCallbackSecret))
internalGroup.POST("/sync-catalog", func(c *gin.Context) {
var req service.SyncCatalogRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, service.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := baishunService.SyncCatalog(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
internalGroup.POST("/refresh-room-state", func(c *gin.Context) {
var req struct {
SysOrigin string `json:"sysOrigin"`
RoomID string `json:"roomId"`
}
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, service.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := baishunService.RefreshRoomState(c.Request.Context(), req.SysOrigin, req.RoomID)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
callbackGroup := router.Group("/game/baishun")
callbackGroup.POST("/token", func(c *gin.Context) {
raw, payload := readRawPayload(c)
writeBaishunJSON(c, http.StatusOK, baishunService.HandleToken(c.Request.Context(), mapToTokenRequest(payload), raw))
})
callbackGroup.POST("/profile", func(c *gin.Context) {
raw, payload := readRawPayload(c)
writeBaishunJSON(c, http.StatusOK, baishunService.HandleProfile(c.Request.Context(), mapToProfileRequest(payload), raw))
})
callbackGroup.POST("/update-token", func(c *gin.Context) {
raw, payload := readRawPayload(c)
writeBaishunJSON(c, http.StatusOK, baishunService.HandleUpdateToken(c.Request.Context(), mapToUpdateTokenRequest(payload), raw))
})
callbackGroup.POST("/change-balance", func(c *gin.Context) {
raw, payload := readRawPayload(c)
writeBaishunJSON(c, http.StatusOK, baishunService.HandleChangeBalance(c.Request.Context(), mapToChangeBalanceRequest(payload), raw))
})
callbackGroup.POST("/report", func(c *gin.Context) {
raw, payload := readRawPayload(c)
writeBaishunJSON(c, http.StatusOK, baishunService.HandleReport(c.Request.Context(), payload, raw))
})
callbackGroup.POST("/balance-info", func(c *gin.Context) {
raw, payload := readRawPayload(c)
writeBaishunJSON(c, http.StatusOK, baishunService.HandleBalanceInfo(c.Request.Context(), mapToBalanceInfoRequest(payload), raw))
})
}
func internalSecretMiddleware(secret string) gin.HandlerFunc {
return func(c *gin.Context) {
if strings.TrimSpace(secret) == "" {
c.Next()
return
}
token := strings.TrimSpace(c.GetHeader("X-Internal-Token"))
if token != secret {
writeError(c, service.NewAppError(http.StatusUnauthorized, "invalid_internal_token", "internal token is invalid"))
c.Abort()
return
}
c.Next()
}
}
func writeBaishunJSON(c *gin.Context, status int, payload any) {
c.JSON(status, payload)
}
func readRawPayload(c *gin.Context) (string, map[string]any) {
rawBytes, _ := c.GetRawData()
raw := string(rawBytes)
payload := map[string]any{}
if len(rawBytes) > 0 {
_ = json.Unmarshal(rawBytes, &payload)
}
return raw, payload
}
func mapToTokenRequest(payload map[string]any) service.BaishunTokenRequest {
return service.BaishunTokenRequest{
AppID: asInt64(payload["app_id"]),
UserID: asString(payload["user_id"]),
Code: asString(payload["code"]),
Signature: asString(payload["signature"]),
SignatureNonce: asString(payload["signature_nonce"]),
Timestamp: asInt64(payload["timestamp"]),
}
}
func mapToProfileRequest(payload map[string]any) service.BaishunProfileRequest {
return service.BaishunProfileRequest{
AppID: asInt64(payload["app_id"]),
UserID: asString(payload["user_id"]),
SSToken: asString(payload["ss_token"]),
ClientIP: asString(payload["client_ip"]),
GameID: int(asInt64(payload["game_id"])),
Signature: asString(payload["signature"]),
SignatureNonce: asString(payload["signature_nonce"]),
Timestamp: asInt64(payload["timestamp"]),
}
}
func mapToUpdateTokenRequest(payload map[string]any) service.BaishunUpdateTokenRequest {
return service.BaishunUpdateTokenRequest{
AppID: asInt64(payload["app_id"]),
UserID: asString(payload["user_id"]),
SSToken: asString(payload["ss_token"]),
GameID: int(asInt64(payload["game_id"])),
Signature: asString(payload["signature"]),
SignatureNonce: asString(payload["signature_nonce"]),
Timestamp: asInt64(payload["timestamp"]),
}
}
func mapToChangeBalanceRequest(payload map[string]any) service.BaishunChangeBalanceRequest {
var currencyType *int
if rawValue, exists := payload["currency_type"]; exists && rawValue != nil {
parsed := int(asInt64(rawValue))
currencyType = &parsed
}
return service.BaishunChangeBalanceRequest{
AppID: asInt64(payload["app_id"]),
UserID: asString(payload["user_id"]),
SSToken: asString(payload["ss_token"]),
CurrencyDiff: asInt64(payload["currency_diff"]),
DiffMsg: asString(payload["diff_msg"]),
GameID: int(asInt64(payload["game_id"])),
GameRoundID: asString(payload["game_round_id"]),
RoomID: asString(payload["room_id"]),
ChangeTimeAt: asInt64(payload["change_time_at"]),
OrderID: asString(payload["order_id"]),
Extend: asString(payload["extend"]),
MsgType: asString(payload["msg_type"]),
CurrencyType: currencyType,
Signature: asString(payload["signature"]),
SignatureNonce: asString(payload["signature_nonce"]),
Timestamp: asInt64(payload["timestamp"]),
}
}
func mapToBalanceInfoRequest(payload map[string]any) service.BaishunBalanceInfoRequest {
return service.BaishunBalanceInfoRequest{
UserID: asString(payload["user_id"]),
AppID: asInt64(payload["app_id"]),
AppChannel: asString(payload["app_channel"]),
Signature: asString(payload["signature"]),
SignatureNonce: asString(payload["signature_nonce"]),
Timestamp: asInt64(payload["timestamp"]),
}
}
func asString(value any) string {
if value == nil {
return ""
}
switch typed := value.(type) {
case string:
return strings.TrimSpace(typed)
case json.Number:
return typed.String()
default:
return strings.TrimSpace(fmt.Sprint(typed))
}
}
func asInt64(value any) int64 {
if value == nil {
return 0
}
switch typed := value.(type) {
case int64:
return typed
case int:
return int64(typed)
case float64:
return int64(typed)
case json.Number:
parsed, _ := typed.Int64()
return parsed
case string:
parsed, _ := strconv.ParseInt(strings.TrimSpace(typed), 10, 64)
return parsed
default:
parsed, _ := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(typed)), 10, 64)
return parsed
}
}

204
internal/http/router.go Normal file
View File

@ -0,0 +1,204 @@
package http
import (
"chatapp3-golang/internal/config"
"chatapp3-golang/internal/integration"
"chatapp3-golang/internal/repo"
"chatapp3-golang/internal/service"
"context"
"errors"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
)
const authUserContextKey = "auth_user"
func NewRouter(
cfg config.Config,
repository *repo.Repository,
javaClient *integration.Client,
inviteService *service.InviteService,
baishunService *service.BaishunService,
) *gin.Engine {
router := gin.New()
router.Use(gin.Logger(), gin.Recovery())
router.GET("/health", func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), cfg.Timeout)
defer cancel()
if err := repository.Ping(ctx); err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{
"code": "dependency_unavailable",
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{"code": "ok", "message": "success"})
})
router.GET("/public/h5/invite-campaign/landing", func(c *gin.Context) {
resp, err := inviteService.GetLanding(c.Request.Context(), c.Query("code"))
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
internal := router.Group("/internal/invite-campaign")
internal.POST("/recharge-success", func(c *gin.Context) {
var req service.RechargeSuccessRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, service.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := inviteService.HandleRechargeSuccess(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
authenticated := router.Group("/app/h5/invite-campaign")
authenticated.Use(authMiddleware(javaClient))
authenticated.GET("/home", func(c *gin.Context) {
user := mustAuthUser(c)
resp, err := inviteService.GetHome(c.Request.Context(), user)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
authenticated.POST("/bind-code", func(c *gin.Context) {
user := mustAuthUser(c)
var req service.BindCodeRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, service.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := inviteService.BindCode(c.Request.Context(), user, resolveClientIP(c), req)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
authenticated.POST("/tasks/claim", func(c *gin.Context) {
user := mustAuthUser(c)
var req service.TaskClaimRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, service.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := inviteService.ClaimTask(c.Request.Context(), user, req)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
registerBaishunRoutes(router, cfg, javaClient, baishunService)
return router
}
func authMiddleware(javaClient *integration.Client) gin.HandlerFunc {
return func(c *gin.Context) {
authorization := strings.TrimSpace(c.GetHeader("Authorization"))
if authorization == "" {
writeError(c, service.NewAppError(http.StatusUnauthorized, "missing_authorization", "authorization is required"))
c.Abort()
return
}
token := trimBearer(authorization)
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
defer cancel()
credential, err := javaClient.AuthenticateToken(ctx, token)
if err != nil {
writeError(c, service.NewAppError(http.StatusUnauthorized, "invalid_token", err.Error()))
c.Abort()
return
}
c.Set(authUserContextKey, service.AuthUser{
UserID: int64(credential.UserID),
SysOrigin: credential.SysOrigin,
Token: token,
Authorization: authorization,
})
c.Next()
}
}
func mustAuthUser(c *gin.Context) service.AuthUser {
value, exists := c.Get(authUserContextKey)
if !exists {
return service.AuthUser{}
}
user, _ := value.(service.AuthUser)
return user
}
func writeOK(c *gin.Context, data any) {
c.JSON(http.StatusOK, gin.H{
"status": true,
"errorCode": 0,
"errorMsg": "success",
"body": data,
"code": "success",
"message": "success",
"data": data,
"time": time.Now().UnixMilli(),
})
}
func writeError(c *gin.Context, err error) {
var appErr *service.AppError
if errors.As(err, &appErr) {
c.JSON(appErr.Status, gin.H{
"status": false,
"errorCode": appErr.Status,
"errorCodeName": appErr.Code,
"errorMsg": appErr.Message,
"body": nil,
"code": appErr.Code,
"message": appErr.Message,
"data": nil,
"time": time.Now().UnixMilli(),
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"status": false,
"errorCode": http.StatusInternalServerError,
"errorCodeName": "internal_error",
"errorMsg": err.Error(),
"body": nil,
"code": "internal_error",
"message": err.Error(),
"data": nil,
"time": time.Now().UnixMilli(),
})
}
func trimBearer(authorization string) string {
if strings.HasPrefix(strings.ToLower(authorization), "bearer ") {
return strings.TrimSpace(authorization[7:])
}
return authorization
}
func resolveClientIP(c *gin.Context) string {
if forwarded := strings.TrimSpace(c.GetHeader("X-Forwarded-For")); forwarded != "" {
parts := strings.Split(forwarded, ",")
if len(parts) > 0 {
return strings.TrimSpace(parts[0])
}
}
return strings.TrimSpace(c.ClientIP())
}

View File

@ -0,0 +1,304 @@
package integration
import (
"bytes"
"chatapp3-golang/internal/config"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
)
type Client struct {
cfg config.Config
httpClient *http.Client
}
type UserCredential struct {
UserID Int64Value `json:"userId"`
SysOrigin string `json:"sysOrigin"`
}
type UserProfile struct {
ID Int64Value `json:"id"`
UserAvatar string `json:"userAvatar"`
UserNickname string `json:"userNickname"`
}
type GoldReceiptCommand struct {
ReceiptType string `json:"receiptType"`
UserID int64 `json:"userId"`
SysOrigin string `json:"sysOrigin"`
EventID string `json:"eventId"`
Remark string `json:"remark,omitempty"`
Amount int64 `json:"amount"`
CloseDelayAsset bool `json:"closeDelayAsset"`
OpUserType string `json:"opUserType"`
CustomizeOrigin string `json:"customizeOrigin,omitempty"`
CustomizeOriginDesc string `json:"customizeOriginDesc,omitempty"`
}
type TestGoldReceiptCommand struct {
ReceiptType string `json:"receiptType"`
UserID int64 `json:"userId"`
EventType string `json:"eventType"`
EventDesc string `json:"eventDesc"`
EventID string `json:"eventId"`
SysOrigin string `json:"sysOrigin"`
Remark string `json:"remark,omitempty"`
Amount int64 `json:"amount"`
CloseDelayAsset bool `json:"closeDelayAsset"`
}
type InviteCode struct {
UserID Int64Value `json:"userId"`
InviteCode string `json:"inviteCode"`
HasBound bool `json:"hasBound"`
}
type GrantGoldRequest struct {
UserID int64 `json:"userId"`
SysOrigin string `json:"sysOrigin"`
EventID string `json:"eventId"`
Origin string `json:"origin"`
GoldAmount int64 `json:"goldAmount"`
Remark string `json:"remark"`
}
type GrantPropsRequest struct {
TrackID int64 `json:"trackId"`
AcceptUserID int64 `json:"acceptUserId"`
SysOrigin string `json:"sysOrigin"`
Origin string `json:"origin"`
SourceGroupID int64 `json:"sourceGroupId"`
}
type resultResponse[T any] struct {
Code int `json:"code"`
Message string `json:"message"`
Body T `json:"body"`
Success *bool `json:"success"`
}
type Int64Value int64
func (v *Int64Value) UnmarshalJSON(data []byte) error {
raw := strings.TrimSpace(string(data))
if raw == "" || raw == "null" {
*v = 0
return nil
}
if strings.HasPrefix(raw, "\"") && strings.HasSuffix(raw, "\"") {
raw = strings.Trim(raw, "\"")
}
parsed, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return err
}
*v = Int64Value(parsed)
return nil
}
func New(cfg config.Config) *Client {
return &Client{
cfg: cfg,
httpClient: &http.Client{
Timeout: cfg.Timeout,
},
}
}
func (c *Client) AuthenticateToken(ctx context.Context, token string) (UserCredential, error) {
endpoint := c.cfg.JavaAuthBaseURL + "/auth/client/getUserCredentialByToken?token=" + url.QueryEscape(token)
var resp resultResponse[UserCredential]
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
return UserCredential{}, err
}
if int64(resp.Body.UserID) == 0 {
return UserCredential{}, fmt.Errorf("empty credential response")
}
return resp.Body, nil
}
func (c *Client) GetDeviceFingerprint(ctx context.Context, userID int64) (string, error) {
endpoint := c.cfg.JavaDeviceBaseURL + "/user/device/fingerprint?userId=" + url.QueryEscape(strconv.FormatInt(userID, 10))
var resp resultResponse[string]
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
return "", err
}
return strings.TrimSpace(resp.Body), nil
}
func (c *Client) EnsureInviteCode(ctx context.Context, authorization string) (InviteCode, error) {
endpoint := c.cfg.JavaAppBaseURL + "/activity/invite/user/my/invite/code"
headers := http.Header{}
if authorization != "" {
headers.Set("Authorization", authorization)
}
var resp InviteCode
if err := c.getJSON(ctx, endpoint, headers, &resp); err != nil {
return InviteCode{}, err
}
if strings.TrimSpace(resp.InviteCode) == "" {
return InviteCode{}, fmt.Errorf("invite code response is empty")
}
return resp, nil
}
func (c *Client) GrantGold(ctx context.Context, req GrantGoldRequest) error {
return c.postJSON(ctx, c.cfg.JavaBridgeBaseURL+"/internal/resident-activity/invite/grant-gold", req, nil, nil)
}
func (c *Client) GrantProps(ctx context.Context, req GrantPropsRequest) error {
return c.postJSON(ctx, c.cfg.JavaBridgeBaseURL+"/internal/resident-activity/invite/grant-props", req, nil, nil)
}
func (c *Client) GetUserProfile(ctx context.Context, userID int64) (UserProfile, error) {
endpoint := c.cfg.JavaOtherBaseURL + "/user-profile/client/getByUserId?userId=" + url.QueryEscape(strconv.FormatInt(userID, 10))
var resp resultResponse[UserProfile]
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
return UserProfile{}, err
}
if int64(resp.Body.ID) == 0 {
return UserProfile{}, fmt.Errorf("empty user profile response")
}
return resp.Body, nil
}
func (c *Client) GetUserLanguage(ctx context.Context, userID int64) (string, error) {
endpoint := c.cfg.JavaOtherBaseURL + "/user-profile/client/getLanguage?userId=" + url.QueryEscape(strconv.FormatInt(userID, 10))
var resp resultResponse[string]
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
return "", err
}
return strings.TrimSpace(resp.Body), nil
}
func (c *Client) MapGoldBalance(ctx context.Context, userIDs []int64) (map[int64]int64, error) {
endpoint := c.cfg.JavaWalletBaseURL + "/wallet/gold/client/mapBalance"
var resp resultResponse[map[string]int64]
if err := c.postJSON(ctx, endpoint, userIDs, nil, &resp); err != nil {
return nil, err
}
result := make(map[int64]int64, len(resp.Body))
for key, value := range resp.Body {
userID, err := strconv.ParseInt(key, 10, 64)
if err != nil {
continue
}
result[userID] = value
}
return result, nil
}
func (c *Client) ExistsGoldEvent(ctx context.Context, eventID string) (bool, error) {
endpoint := c.cfg.JavaWalletBaseURL + "/wallet/gold/client/existsEventId?eventId=" + url.QueryEscape(eventID)
var resp resultResponse[bool]
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
return false, err
}
return resp.Body, nil
}
func (c *Client) ChangeGoldBalance(ctx context.Context, cmd GoldReceiptCommand) error {
endpoint := c.cfg.JavaWalletBaseURL + "/wallet/gold/client/balance/change"
if err := c.postJSON(ctx, endpoint, cmd, nil, nil); err == nil {
return nil
} else if !shouldFallbackToGoldTestEndpoint(err) {
return err
}
// Some local environments only expose the lighter test endpoint payload.
testCmd := TestGoldReceiptCommand{
ReceiptType: cmd.ReceiptType,
UserID: cmd.UserID,
EventType: cmd.CustomizeOrigin,
EventDesc: cmd.CustomizeOriginDesc,
EventID: cmd.EventID,
SysOrigin: cmd.SysOrigin,
Remark: cmd.Remark,
Amount: cmd.Amount,
CloseDelayAsset: cmd.CloseDelayAsset,
}
return c.postJSON(ctx, c.cfg.JavaWalletBaseURL+"/wallet/gold/client/balance/change/test", testCmd, nil, nil)
}
func shouldFallbackToGoldTestEndpoint(err error) bool {
if err == nil {
return false
}
message := strings.ToLower(strings.TrimSpace(err.Error()))
switch {
case strings.Contains(message, "status=404"),
strings.Contains(message, "status=405"),
strings.Contains(message, "status=501"),
strings.Contains(message, "connection refused"),
strings.Contains(message, "no such host"),
strings.Contains(message, "unsupported protocol"):
return true
default:
return false
}
}
func (c *Client) getJSON(ctx context.Context, endpoint string, headers http.Header, out any) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return err
}
applyHeaders(req.Header, headers)
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return decodeResponse(resp, out)
}
func (c *Client) postJSON(ctx context.Context, endpoint string, payload any, headers http.Header, out any) error {
data, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(data))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
applyHeaders(req.Header, headers)
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return decodeResponse(resp, out)
}
func decodeResponse(resp *http.Response, out any) error {
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("java api failed: status=%d body=%s", resp.StatusCode, strings.TrimSpace(string(body)))
}
if out == nil || len(body) == 0 {
return nil
}
if err := json.Unmarshal(body, out); err != nil {
return fmt.Errorf("decode response failed: %w body=%s", err, strings.TrimSpace(string(body)))
}
return nil
}
func applyHeaders(dst http.Header, src http.Header) {
for key, values := range src {
for _, value := range values {
dst.Add(key, value)
}
}
}

View File

@ -0,0 +1,162 @@
package model
import "time"
type SysGameListConfig struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32"`
GameOrigin string `gorm:"column:game_origin;size:32"`
GameID string `gorm:"column:game_id;size:64"`
Name string `gorm:"column:name;size:128"`
Category string `gorm:"column:category;size:64"`
GameCode string `gorm:"column:game_code;size:512"`
Cover string `gorm:"column:cover;size:1024"`
Showcase bool `gorm:"column:is_showcase"`
Sort int64 `gorm:"column:sort"`
FullScreen bool `gorm:"column:full_screen"`
ClientOrigin string `gorm:"column:client_origin;size:32"`
Regions string `gorm:"column:regions;size:255"`
GameMode string `gorm:"column:game_mode;size:255"`
}
func (SysGameListConfig) TableName() string { return "sys_game_list_config" }
type SysGameListVendorExt struct {
ID int64 `gorm:"column:id;primaryKey"`
GameListConfigID int64 `gorm:"column:game_list_config_id;uniqueIndex:uk_game_vendor_ext_cfg_vendor,priority:1"`
SysOrigin string `gorm:"column:sys_origin;size:32;index:idx_game_vendor_ext_sys_vendor,priority:1"`
VendorType string `gorm:"column:vendor_type;size:32;uniqueIndex:uk_game_vendor_ext_cfg_vendor,priority:2;index:idx_game_vendor_ext_sys_vendor,priority:2"`
VendorGameID string `gorm:"column:vendor_game_id;size:64"`
LaunchMode string `gorm:"column:launch_mode;size:32"`
PackageVersion string `gorm:"column:package_version;size:32"`
PackageURL string `gorm:"column:package_url;size:1024"`
PreviewURL string `gorm:"column:preview_url;size:1024"`
Orientation *int `gorm:"column:orientation"`
SafeHeight int `gorm:"column:safe_height"`
GSP string `gorm:"column:gsp;size:64"`
BridgeSchemaVersion string `gorm:"column:bridge_schema_version;size:16"`
CurrencyType *int `gorm:"column:currency_type"`
CurrencyIcon string `gorm:"column:currency_icon;size:1024"`
ExtraJSON string `gorm:"column:extra_json;type:text"`
Enabled bool `gorm:"column:enabled;index:idx_game_vendor_ext_sys_vendor,priority:3"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (SysGameListVendorExt) TableName() string { return "sys_game_list_vendor_ext" }
type BaishunGameCatalog struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32;uniqueIndex:uk_baishun_catalog_sys_vendor_game,priority:1;index:idx_baishun_catalog_internal_game,priority:1"`
InternalGameID string `gorm:"column:internal_game_id;size:64;index:idx_baishun_catalog_internal_game,priority:2"`
VendorGameID int `gorm:"column:vendor_game_id;uniqueIndex:uk_baishun_catalog_sys_vendor_game,priority:2"`
Name string `gorm:"column:name;size:128"`
Cover string `gorm:"column:cover;size:1024"`
PreviewURL string `gorm:"column:preview_url;size:1024"`
DownloadURL string `gorm:"column:download_url;size:1024"`
PackageVersion string `gorm:"column:package_version;size:32"`
GameModeJSON string `gorm:"column:game_mode_json;size:64"`
Orientation *int `gorm:"column:orientation"`
SafeHeight int `gorm:"column:safe_height"`
VenueLevelJSON string `gorm:"column:venue_level_json;size:64"`
GSP string `gorm:"column:gsp;size:64"`
RawJSON string `gorm:"column:raw_json;type:longtext"`
Status string `gorm:"column:status;size:32;index:idx_baishun_catalog_internal_game,priority:3"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (BaishunGameCatalog) TableName() string { return "baishun_game_catalog" }
type BaishunLaunchSession struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32;index:idx_baishun_session_room_user,priority:1"`
RoomID string `gorm:"column:room_id;size:64;index:idx_baishun_session_room_user,priority:2"`
UserID int64 `gorm:"column:user_id;index:idx_baishun_session_room_user,priority:3"`
InternalGameID string `gorm:"column:internal_game_id;size:64"`
VendorGameID int `gorm:"column:vendor_game_id"`
GameSessionID string `gorm:"column:game_session_id;size:64;uniqueIndex:uk_baishun_game_session"`
LaunchCode string `gorm:"column:launch_code;size:128;uniqueIndex:uk_baishun_launch_code"`
LaunchCodeExpireTime time.Time `gorm:"column:launch_code_expire_time"`
SSToken *string `gorm:"column:ss_token;size:255;uniqueIndex:uk_baishun_ss_token"`
SSTokenExpireTime *time.Time `gorm:"column:ss_token_expire_time;index:idx_baishun_session_token_expire"`
Language string `gorm:"column:language;size:16"`
GSP int `gorm:"column:gsp"`
CurrencyType int `gorm:"column:currency_type"`
CurrencyIcon string `gorm:"column:currency_icon;size:1024"`
SceneMode int `gorm:"column:scene_mode"`
GameMode int `gorm:"column:game_mode"`
PackageVersion string `gorm:"column:package_version;size:32"`
ClientIP string `gorm:"column:client_ip;size:64"`
ClientOrigin string `gorm:"column:client_origin;size:16"`
Status string `gorm:"column:status;size:32;index:idx_baishun_session_room_user,priority:4"`
ClosedReason string `gorm:"column:closed_reason;size:64"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (BaishunLaunchSession) TableName() string { return "baishun_launch_session" }
type BaishunOrderIdempotency struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32;uniqueIndex:uk_baishun_order,priority:1;index:idx_baishun_order_user_time,priority:1"`
OrderID string `gorm:"column:order_id;size:128;uniqueIndex:uk_baishun_order,priority:2"`
GameRoundID string `gorm:"column:game_round_id;size:128;index:idx_baishun_round"`
RoomID string `gorm:"column:room_id;size:64"`
UserID int64 `gorm:"column:user_id;index:idx_baishun_order_user_time,priority:2"`
VendorGameID int `gorm:"column:vendor_game_id"`
CurrencyDiff int64 `gorm:"column:currency_diff"`
DiffMsg string `gorm:"column:diff_msg;size:32"`
MsgType string `gorm:"column:msg_type;size:64"`
CurrencyType *int `gorm:"column:currency_type"`
WalletEventID string `gorm:"column:wallet_event_id;size:160;uniqueIndex:uk_baishun_wallet_event"`
WalletAssetRecordID *int64 `gorm:"column:wallet_asset_record_id"`
Status string `gorm:"column:status;size:32"`
RequestJSON string `gorm:"column:request_json;type:longtext"`
ResponseJSON string `gorm:"column:response_json;type:longtext"`
CreateTime time.Time `gorm:"column:create_time;index:idx_baishun_order_user_time,priority:3"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (BaishunOrderIdempotency) TableName() string { return "baishun_order_idempotency" }
type BaishunCallbackLog struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32"`
RequestType string `gorm:"column:request_type;size:32;index:idx_baishun_log_type_time,priority:1"`
OrderID string `gorm:"column:order_id;size:128;index:idx_baishun_log_order"`
GameRoundID string `gorm:"column:game_round_id;size:128"`
RoomID string `gorm:"column:room_id;size:64;index:idx_baishun_log_room_user,priority:1"`
UserID *int64 `gorm:"column:user_id;index:idx_baishun_log_room_user,priority:2"`
VendorGameID *int `gorm:"column:vendor_game_id"`
RequestJSON string `gorm:"column:request_json;type:longtext"`
ResponseJSON string `gorm:"column:response_json;type:longtext"`
BizCode string `gorm:"column:biz_code;size:32"`
BizMessage string `gorm:"column:biz_message;size:255"`
Status string `gorm:"column:status;size:32"`
CreateTime time.Time `gorm:"column:create_time;index:idx_baishun_log_type_time,priority:2"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (BaishunCallbackLog) TableName() string { return "baishun_callback_log" }
type BaishunRoomState struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32;uniqueIndex:uk_baishun_room_state,priority:1"`
RoomID string `gorm:"column:room_id;size:64;uniqueIndex:uk_baishun_room_state,priority:2"`
HostUserID int64 `gorm:"column:host_user_id"`
CurrentGameID string `gorm:"column:current_game_id;size:64"`
CurrentVendorGameID *int `gorm:"column:current_vendor_game_id"`
CurrentGameName string `gorm:"column:current_game_name;size:128"`
CurrentGameCover string `gorm:"column:current_game_cover;size:1024"`
GameSessionID string `gorm:"column:game_session_id;size:64;index:idx_baishun_room_state_session"`
LaunchUserID *int64 `gorm:"column:launch_user_id"`
State string `gorm:"column:state;size:32"`
ExtraJSON string `gorm:"column:extra_json;type:text"`
StartTime *time.Time `gorm:"column:start_time"`
EndTime *time.Time `gorm:"column:end_time"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (BaishunRoomState) TableName() string { return "baishun_room_state" }

149
internal/model/models.go Normal file
View File

@ -0,0 +1,149 @@
package model
import "time"
type InviteCodeMapping struct {
UserID int64 `gorm:"column:user_id;primaryKey"`
InviteCode string `gorm:"column:invite_code;size:32;uniqueIndex:uk_invite_code"`
Account string `gorm:"column:account;size:64"`
}
func (InviteCodeMapping) TableName() string { return "invite_code_mapping" }
type UserBaseInfo struct {
ID int64 `gorm:"column:id;primaryKey"`
Account string `gorm:"column:account;size:64"`
UserAvatar string `gorm:"column:user_avatar;size:255"`
UserNickname string `gorm:"column:user_nickname;size:255"`
OriginSys string `gorm:"column:origin_sys;size:32"`
CreateTime time.Time `gorm:"column:create_time"`
}
func (UserBaseInfo) TableName() string { return "user_base_info" }
type InviteCampaignConfig struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32;uniqueIndex:uk_invite_campaign_sys_origin"`
Enabled bool `gorm:"column:enabled"`
DownloadURL string `gorm:"column:download_url;size:512"`
ShareTitle string `gorm:"column:share_title;size:255"`
ShareDesc string `gorm:"column:share_desc;size:255"`
ServiceContact string `gorm:"column:service_contact;size:255"`
Timezone string `gorm:"column:timezone;size:64"`
AntiCheatSameIPOnce bool `gorm:"column:anti_cheat_same_ip_once"`
AntiCheatSameDeviceOnce bool `gorm:"column:anti_cheat_same_device_once"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (InviteCampaignConfig) TableName() string { return "invite_campaign_config" }
type InviteCampaignRewardRule struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32;index:idx_invite_rule_sys_type_sort,priority:1"`
RuleType string `gorm:"column:rule_type;size:32;index:idx_invite_rule_sys_type_sort,priority:2"`
ThresholdValue int64 `gorm:"column:threshold_value"`
GoldAmount int64 `gorm:"column:gold_amount"`
RewardGroupID *int64 `gorm:"column:reward_group_id"`
Enabled bool `gorm:"column:enabled"`
Sort int `gorm:"column:sort;index:idx_invite_rule_sys_type_sort,priority:3"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (InviteCampaignRewardRule) TableName() string { return "invite_campaign_reward_rule" }
type InviteCampaignRelation struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32;index:idx_invite_relation_sys_inviter,priority:1;index:uk_invite_relation_invitee,priority:1,unique"`
InviterUserID int64 `gorm:"column:inviter_user_id;index:idx_invite_relation_sys_inviter,priority:2"`
InviteeUserID int64 `gorm:"column:invitee_user_id;index:uk_invite_relation_invitee,priority:2,unique"`
InviteCode string `gorm:"column:invite_code;size:32"`
BindIP string `gorm:"column:bind_ip;size:128;index:idx_invite_relation_ip"`
DeviceFingerprint string `gorm:"column:device_fingerprint;size:255;index:idx_invite_relation_device"`
EligibleStatus string `gorm:"column:eligible_status;size:32"`
BlockReason string `gorm:"column:block_reason;size:255"`
BindTime time.Time `gorm:"column:bind_time"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (InviteCampaignRelation) TableName() string { return "invite_campaign_relation" }
type InviteCampaignInviteeProgress struct {
ID int64 `gorm:"column:id;primaryKey"`
RelationID int64 `gorm:"column:relation_id;uniqueIndex:uk_invitee_progress_relation"`
SysOrigin string `gorm:"column:sys_origin;size:32"`
InviterUserID int64 `gorm:"column:inviter_user_id;index:idx_invitee_progress_inviter"`
InviteeUserID int64 `gorm:"column:invitee_user_id;uniqueIndex:uk_invitee_progress_invitee"`
TotalRecharge int64 `gorm:"column:total_recharge_coins"`
ValidUser bool `gorm:"column:valid_user"`
ValidTime *time.Time `gorm:"column:valid_time"`
RewardedRuleIDs string `gorm:"column:rewarded_rule_ids;size:255"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (InviteCampaignInviteeProgress) TableName() string { return "invite_campaign_invitee_progress" }
type InviteCampaignMonthlyProgress struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32;uniqueIndex:uk_invite_monthly_progress,priority:1"`
InviterUserID int64 `gorm:"column:inviter_user_id;uniqueIndex:uk_invite_monthly_progress,priority:2"`
MonthKey string `gorm:"column:month_key;size:6;uniqueIndex:uk_invite_monthly_progress,priority:3"`
InviteCount int64 `gorm:"column:invite_count"`
ValidUserCount int64 `gorm:"column:valid_user_count"`
TotalRecharge int64 `gorm:"column:total_recharge_coins"`
RewardGoldCoins int64 `gorm:"column:reward_gold_coins"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (InviteCampaignMonthlyProgress) TableName() string { return "invite_campaign_monthly_progress" }
type InviteCampaignTaskClaim struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32;uniqueIndex:uk_invite_task_claim,priority:1"`
InviterUserID int64 `gorm:"column:inviter_user_id;uniqueIndex:uk_invite_task_claim,priority:2"`
MonthKey string `gorm:"column:month_key;size:6;uniqueIndex:uk_invite_task_claim,priority:3"`
RuleID int64 `gorm:"column:rule_id;uniqueIndex:uk_invite_task_claim,priority:4"`
ClaimTime time.Time `gorm:"column:claim_time"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (InviteCampaignTaskClaim) TableName() string { return "invite_campaign_task_claim" }
type InviteCampaignRewardLog struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32;index:idx_invite_reward_target"`
RelationID *int64 `gorm:"column:relation_id"`
InviterUserID *int64 `gorm:"column:inviter_user_id"`
InviteeUserID *int64 `gorm:"column:invitee_user_id"`
TargetUserID int64 `gorm:"column:target_user_id;index:idx_invite_reward_target"`
RuleID *int64 `gorm:"column:rule_id"`
RewardScene string `gorm:"column:reward_scene;size:32"`
BusinessKey string `gorm:"column:business_key;size:128;uniqueIndex:uk_invite_reward_business"`
GoldAmount int64 `gorm:"column:gold_amount"`
RewardGroupID *int64 `gorm:"column:reward_group_id"`
Status string `gorm:"column:status;size:32"`
ErrorMessage string `gorm:"column:error_message;size:255"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (InviteCampaignRewardLog) TableName() string { return "invite_campaign_reward_log" }
type InviteCampaignRechargeEvent struct {
ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32"`
OrderID string `gorm:"column:order_id;size:128;uniqueIndex:uk_invite_recharge_order"`
InviterUserID *int64 `gorm:"column:inviter_user_id"`
InviteeUserID int64 `gorm:"column:invitee_user_id"`
RechargeCoins int64 `gorm:"column:recharge_coins"`
PayTime time.Time `gorm:"column:pay_time"`
CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (InviteCampaignRechargeEvent) TableName() string { return "invite_campaign_recharge_event" }

62
internal/repo/repo.go Normal file
View File

@ -0,0 +1,62 @@
package repo
import (
"context"
"chatapp3-golang/internal/config"
"chatapp3-golang/internal/model"
"github.com/redis/go-redis/v9"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Repository struct {
DB *gorm.DB
Redis *redis.Client
}
func New(cfg config.Config) (*Repository, error) {
db, err := gorm.Open(mysql.Open(cfg.MySQLDSN), &gorm.Config{})
if err != nil {
return nil, err
}
client := redis.NewClient(&redis.Options{
Addr: cfg.RedisAddr,
Password: cfg.RedisPassword,
DB: cfg.RedisDB,
})
return &Repository{DB: db, Redis: client}, nil
}
func (r *Repository) Ping(ctx context.Context) error {
if err := r.Redis.Ping(ctx).Err(); err != nil {
return err
}
sqlDB, err := r.DB.DB()
if err != nil {
return err
}
return sqlDB.PingContext(ctx)
}
func (r *Repository) AutoMigrate() error {
return r.DB.AutoMigrate(
&model.InviteCampaignConfig{},
&model.InviteCampaignRewardRule{},
&model.InviteCampaignRelation{},
&model.InviteCampaignInviteeProgress{},
&model.InviteCampaignMonthlyProgress{},
&model.InviteCampaignTaskClaim{},
&model.InviteCampaignRewardLog{},
&model.InviteCampaignRechargeEvent{},
&model.SysGameListVendorExt{},
&model.BaishunGameCatalog{},
&model.BaishunLaunchSession{},
&model.BaishunOrderIdempotency{},
&model.BaishunCallbackLog{},
&model.BaishunRoomState{},
)
}

1403
internal/service/baishun.go Normal file

File diff suppressed because it is too large Load Diff

1527
internal/service/invite.go Normal file

File diff suppressed because it is too large Load Diff

23
internal/util/id.go Normal file
View File

@ -0,0 +1,23 @@
package util
import (
"sync"
"github.com/bwmarrin/snowflake"
)
var (
nodeOnce sync.Once
node *snowflake.Node
nodeErr error
)
func NextID() (int64, error) {
nodeOnce.Do(func() {
node, nodeErr = snowflake.NewNode(1)
})
if nodeErr != nil {
return 0, nodeErr
}
return node.Generate().Int64(), nil
}

View File

@ -0,0 +1,124 @@
CREATE TABLE IF NOT EXISTS invite_campaign_config (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) NOT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 0,
download_url VARCHAR(512) DEFAULT NULL,
share_title VARCHAR(255) DEFAULT NULL,
share_desc VARCHAR(255) DEFAULT NULL,
service_contact VARCHAR(255) DEFAULT NULL,
timezone VARCHAR(64) NOT NULL DEFAULT 'Asia/Riyadh',
anti_cheat_same_ip_once TINYINT(1) NOT NULL DEFAULT 1,
anti_cheat_same_device_once TINYINT(1) NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_invite_campaign_sys_origin (sys_origin)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS invite_campaign_reward_rule (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) NOT NULL,
rule_type VARCHAR(32) NOT NULL,
threshold_value BIGINT NOT NULL DEFAULT 0,
gold_amount BIGINT NOT NULL DEFAULT 0,
reward_group_id BIGINT DEFAULT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
sort INT NOT NULL DEFAULT 0,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_invite_rule_sys_type_sort (sys_origin, rule_type, sort)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS invite_campaign_relation (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) NOT NULL,
inviter_user_id BIGINT NOT NULL,
invitee_user_id BIGINT NOT NULL,
invite_code VARCHAR(32) NOT NULL,
bind_ip VARCHAR(128) DEFAULT NULL,
device_fingerprint VARCHAR(255) DEFAULT NULL,
eligible_status VARCHAR(32) NOT NULL DEFAULT 'ELIGIBLE',
block_reason VARCHAR(255) DEFAULT NULL,
bind_time DATETIME NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_invite_relation_invitee (sys_origin, invitee_user_id),
KEY idx_invite_relation_sys_inviter (sys_origin, inviter_user_id),
KEY idx_invite_relation_ip (bind_ip),
KEY idx_invite_relation_device (device_fingerprint)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS invite_campaign_invitee_progress (
id BIGINT NOT NULL PRIMARY KEY,
relation_id BIGINT NOT NULL,
sys_origin VARCHAR(32) NOT NULL,
inviter_user_id BIGINT NOT NULL,
invitee_user_id BIGINT NOT NULL,
total_recharge_coins BIGINT NOT NULL DEFAULT 0,
valid_user TINYINT(1) NOT NULL DEFAULT 0,
valid_time DATETIME DEFAULT NULL,
rewarded_rule_ids VARCHAR(255) DEFAULT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_invitee_progress_relation (relation_id),
UNIQUE KEY uk_invitee_progress_invitee (invitee_user_id),
KEY idx_invitee_progress_inviter (inviter_user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS invite_campaign_monthly_progress (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) NOT NULL,
inviter_user_id BIGINT NOT NULL,
month_key VARCHAR(6) NOT NULL,
invite_count BIGINT NOT NULL DEFAULT 0,
valid_user_count BIGINT NOT NULL DEFAULT 0,
total_recharge_coins BIGINT NOT NULL DEFAULT 0,
reward_gold_coins BIGINT NOT NULL DEFAULT 0,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_invite_monthly_progress (sys_origin, inviter_user_id, month_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS invite_campaign_task_claim (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) NOT NULL,
inviter_user_id BIGINT NOT NULL,
month_key VARCHAR(6) NOT NULL,
rule_id BIGINT NOT NULL,
claim_time DATETIME NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_invite_task_claim (sys_origin, inviter_user_id, month_key, rule_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS invite_campaign_reward_log (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) NOT NULL,
relation_id BIGINT DEFAULT NULL,
inviter_user_id BIGINT DEFAULT NULL,
invitee_user_id BIGINT DEFAULT NULL,
target_user_id BIGINT NOT NULL,
rule_id BIGINT DEFAULT NULL,
reward_scene VARCHAR(32) NOT NULL,
business_key VARCHAR(128) NOT NULL,
gold_amount BIGINT NOT NULL DEFAULT 0,
reward_group_id BIGINT DEFAULT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
error_message VARCHAR(255) DEFAULT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_invite_reward_business (business_key),
KEY idx_invite_reward_target (sys_origin, target_user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS invite_campaign_recharge_event (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) NOT NULL,
order_id VARCHAR(128) NOT NULL,
inviter_user_id BIGINT DEFAULT NULL,
invitee_user_id BIGINT NOT NULL,
recharge_coins BIGINT NOT NULL DEFAULT 0,
pay_time DATETIME NOT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_invite_recharge_order (order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -0,0 +1,145 @@
CREATE TABLE IF NOT EXISTS sys_game_list_vendor_ext (
id BIGINT NOT NULL PRIMARY KEY,
game_list_config_id BIGINT NOT NULL,
sys_origin VARCHAR(32) NOT NULL,
vendor_type VARCHAR(32) NOT NULL,
vendor_game_id VARCHAR(64) NOT NULL,
launch_mode VARCHAR(32) NOT NULL DEFAULT 'H5_REMOTE',
package_version VARCHAR(32) DEFAULT NULL,
package_url VARCHAR(1024) DEFAULT NULL,
preview_url VARCHAR(1024) DEFAULT NULL,
orientation TINYINT DEFAULT NULL,
safe_height INT NOT NULL DEFAULT 0,
gsp VARCHAR(64) DEFAULT NULL,
bridge_schema_version VARCHAR(16) NOT NULL DEFAULT '1.0',
currency_type INT DEFAULT NULL,
currency_icon VARCHAR(1024) DEFAULT NULL,
extra_json TEXT DEFAULT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_game_vendor_ext_cfg_vendor (game_list_config_id, vendor_type),
KEY idx_game_vendor_ext_sys_vendor (sys_origin, vendor_type, enabled)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS baishun_game_catalog (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) NOT NULL,
internal_game_id VARCHAR(64) NOT NULL,
vendor_game_id INT NOT NULL,
name VARCHAR(128) NOT NULL,
cover VARCHAR(1024) DEFAULT NULL,
preview_url VARCHAR(1024) DEFAULT NULL,
download_url VARCHAR(1024) DEFAULT NULL,
package_version VARCHAR(32) DEFAULT NULL,
game_mode_json VARCHAR(64) DEFAULT NULL,
orientation TINYINT DEFAULT NULL,
safe_height INT NOT NULL DEFAULT 0,
venue_level_json VARCHAR(64) DEFAULT NULL,
gsp VARCHAR(64) DEFAULT NULL,
raw_json LONGTEXT DEFAULT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'ENABLED',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_baishun_catalog_sys_vendor_game (sys_origin, vendor_game_id),
KEY idx_baishun_catalog_internal_game (sys_origin, internal_game_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS baishun_launch_session (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) NOT NULL,
room_id VARCHAR(64) NOT NULL,
user_id BIGINT NOT NULL,
internal_game_id VARCHAR(64) NOT NULL,
vendor_game_id INT NOT NULL,
game_session_id VARCHAR(64) NOT NULL,
launch_code VARCHAR(128) NOT NULL,
launch_code_expire_time DATETIME NOT NULL,
ss_token VARCHAR(255) DEFAULT NULL,
ss_token_expire_time DATETIME DEFAULT NULL,
language VARCHAR(16) NOT NULL DEFAULT '0',
gsp INT NOT NULL DEFAULT 0,
currency_type INT NOT NULL DEFAULT 0,
currency_icon VARCHAR(1024) DEFAULT NULL,
scene_mode INT NOT NULL DEFAULT 0,
game_mode INT NOT NULL DEFAULT 3,
package_version VARCHAR(32) DEFAULT NULL,
client_ip VARCHAR(64) DEFAULT NULL,
client_origin VARCHAR(16) DEFAULT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'INIT',
closed_reason VARCHAR(64) DEFAULT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_baishun_launch_code (launch_code),
UNIQUE KEY uk_baishun_ss_token (ss_token),
UNIQUE KEY uk_baishun_game_session (game_session_id),
KEY idx_baishun_session_room_user (sys_origin, room_id, user_id, status),
KEY idx_baishun_session_token_expire (ss_token_expire_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS baishun_order_idempotency (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) NOT NULL,
order_id VARCHAR(128) NOT NULL,
game_round_id VARCHAR(128) DEFAULT NULL,
room_id VARCHAR(64) NOT NULL,
user_id BIGINT NOT NULL,
vendor_game_id INT NOT NULL,
currency_diff BIGINT NOT NULL DEFAULT 0,
diff_msg VARCHAR(32) NOT NULL,
msg_type VARCHAR(64) DEFAULT NULL,
currency_type INT DEFAULT NULL,
wallet_event_id VARCHAR(160) NOT NULL,
wallet_asset_record_id BIGINT DEFAULT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'INIT',
request_json LONGTEXT DEFAULT NULL,
response_json LONGTEXT DEFAULT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_baishun_order (sys_origin, order_id),
UNIQUE KEY uk_baishun_wallet_event (wallet_event_id),
KEY idx_baishun_round (game_round_id),
KEY idx_baishun_order_user_time (sys_origin, user_id, create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS baishun_callback_log (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) DEFAULT NULL,
request_type VARCHAR(32) NOT NULL,
order_id VARCHAR(128) DEFAULT NULL,
game_round_id VARCHAR(128) DEFAULT NULL,
room_id VARCHAR(64) DEFAULT NULL,
user_id BIGINT DEFAULT NULL,
vendor_game_id INT DEFAULT NULL,
request_json LONGTEXT DEFAULT NULL,
response_json LONGTEXT DEFAULT NULL,
biz_code VARCHAR(32) DEFAULT NULL,
biz_message VARCHAR(255) DEFAULT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'SUCCESS',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_baishun_log_type_time (request_type, create_time),
KEY idx_baishun_log_order (order_id),
KEY idx_baishun_log_room_user (room_id, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS baishun_room_state (
id BIGINT NOT NULL PRIMARY KEY,
sys_origin VARCHAR(32) NOT NULL,
room_id VARCHAR(64) NOT NULL,
host_user_id BIGINT NOT NULL,
current_game_id VARCHAR(64) DEFAULT NULL,
current_vendor_game_id INT DEFAULT NULL,
current_game_name VARCHAR(128) DEFAULT NULL,
current_game_cover VARCHAR(1024) DEFAULT NULL,
game_session_id VARCHAR(64) DEFAULT NULL,
launch_user_id BIGINT DEFAULT NULL,
state VARCHAR(32) NOT NULL DEFAULT 'IDLE',
extra_json TEXT DEFAULT NULL,
start_time DATETIME DEFAULT NULL,
end_time DATETIME DEFAULT NULL,
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_baishun_room_state (sys_origin, room_id),
KEY idx_baishun_room_state_session (game_session_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;