diff --git a/code/allapp/allapp-go-v3/configs/config.dev.yaml b/code/allapp/allapp-go-v3/configs/config.dev.yaml index 5a3b584a..08032689 100644 --- a/code/allapp/allapp-go-v3/configs/config.dev.yaml +++ b/code/allapp/allapp-go-v3/configs/config.dev.yaml @@ -5,9 +5,15 @@ postgres: port: 5432 user: postgres password: zhang520.. - dbname: app + dbname: allapp timezone: Asia/Shanghai max_open_conns: 10 max_idle_conns: 5 conn_max_lifetime: 30m - sll_mode: false \ No newline at end of file + sll_mode: false +aws_s3: + region: us-east-1 + access_key: 2c0sZEpPQyCVhoLGNzHS + secret_key: fRbcQPEhU8B0C3On4qIoJ19w5vXxTFD26LlAiae7 + endpoint: http://118.89.70.199:9000 + bucket: test \ No newline at end of file diff --git a/code/allapp/allapp-go-v3/go.mod b/code/allapp/allapp-go-v3/go.mod index a080261e..3b0b6cbd 100644 --- a/code/allapp/allapp-go-v3/go.mod +++ b/code/allapp/allapp-go-v3/go.mod @@ -15,6 +15,24 @@ require ( require ( github.com/andybalholm/brotli v1.2.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.15 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.14 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect + github.com/aws/smithy-go v1.24.2 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect diff --git a/code/allapp/allapp-go-v3/go.sum b/code/allapp/allapp-go-v3/go.sum index f98ce49b..27f5207f 100644 --- a/code/allapp/allapp-go-v3/go.sum +++ b/code/allapp/allapp-go-v3/go.sum @@ -1,5 +1,41 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= +github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= +github.com/aws/aws-sdk-go-v2/config v1.32.15 h1:i7rHbaySnBXGvCkDndaBU8f3EAlRVgViwNfkwFUrXgE= +github.com/aws/aws-sdk-go-v2/config v1.32.15/go.mod h1:yLJzL0IkI9+4BwjPSOueyHzppJj3t0dhK5tbmmcFk5Q= +github.com/aws/aws-sdk-go-v2/credentials v1.19.14 h1:n+UcGWAIZHkXzYt87uMFBv/l8THYELoX6gVcUvgl6fI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.14/go.mod h1:cJKuyWB59Mqi0jM3nFYQRmnHVQIcgoxjEMAbLkpr62w= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 h1:NUS3K4BTDArQqNu2ih7yeDLaS3bmHD0YndtA6UP884g= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21/go.mod h1:YWNWJQNjKigKY1RHVJCuupeWDrrHjRqHm0N9rdrWzYI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0 h1:hlSuz394kV0vhv9drL5lhuEFbEOEP1VyQpy15qWh1Pk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 h1:QKZH0S178gCmFEgst8hN0mCX1KxLgHBKKY/CLqwP8lg= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.9/go.mod h1:7yuQJoT+OoH8aqIxw9vwF+8KpvLZ8AWmvmUWHsGQZvI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 h1:lFd1+ZSEYJZYvv9d6kXzhkZu07si3f+GQ1AaYwa2LUM= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.15/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 h1:dzztQ1YmfPrxdrOiuZRMF6fuOwWlWpD2StNLTceKpys= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19/go.mod h1:YO8TrYtFdl5w/4vmjL8zaBSsiNp3w0L1FfKVKenZT7w= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 h1:p8ogvvLugcR/zLBXTXrTkj0RYBUdErbMnAFFp12Lm/U= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= 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= diff --git a/code/allapp/allapp-go-v3/internal/bootstrap/app.go b/code/allapp/allapp-go-v3/internal/bootstrap/app.go index 10884628..0a20fb86 100644 --- a/code/allapp/allapp-go-v3/internal/bootstrap/app.go +++ b/code/allapp/allapp-go-v3/internal/bootstrap/app.go @@ -5,6 +5,8 @@ import ( "allapp-go/internal/config" "allapp-go/pkg/db" "allapp-go/pkg/logger" + "allapp-go/pkg/s3store" + "allapp-go/pkg/wechat" "github.com/jackc/pgx/v5/pgxpool" ) @@ -27,6 +29,9 @@ func NewApp() (*App, error) { // 日志 boots.InitLogger(cfg) + // S3 + s3store.NewS3Client(cfg) + // 数据库 pgsql, err := boots.InitPgsql(cfg) if err != nil { @@ -38,6 +43,7 @@ func NewApp() (*App, error) { // 其他组件 boots.InitUniqueId(cfg) boots.InitJwt(cfg) + wechat.Init(cfg.Wechat.AppId, cfg.Wechat.AppSecret) return &App{ Config: cfg, diff --git a/code/allapp/allapp-go-v3/internal/config/config.go b/code/allapp/allapp-go-v3/internal/config/config.go index b1125e1e..a4f7c64d 100644 --- a/code/allapp/allapp-go-v3/internal/config/config.go +++ b/code/allapp/allapp-go-v3/internal/config/config.go @@ -8,6 +8,7 @@ type Config struct { UniqueID UniqueIDConfig `mapstructure:"unique_id"` Postgres PostgresConfig `mapstructure:"postgres"` Wechat WechatConfig `mapstructure:"wechat"` + AwsS3 AwsS3Config `mapstructure:"aws_s3"` } type AppConfig struct { @@ -43,3 +44,11 @@ type WechatConfig struct { AppId string `mapstructure:"app_id"` AppSecret string `mapstructure:"app_secret"` } + +type AwsS3Config struct { + Region string `mapstructure:"region"` + AccessKey string `mapstructure:"access_key"` + SecretKey string `mapstructure:"secret_key"` + Bucket string `mapstructure:"bucket"` + Endpoint string `mapstructure:"endpoint"` +} diff --git a/code/allapp/allapp-go-v3/internal/handle/auth.go b/code/allapp/allapp-go-v3/internal/handle/auth.go index a89b78ca..358ad111 100644 --- a/code/allapp/allapp-go-v3/internal/handle/auth.go +++ b/code/allapp/allapp-go-v3/internal/handle/auth.go @@ -94,8 +94,8 @@ func handleThirdLogin( } // ========= token ========= - token, err := jwtx.CreateToken(c.Context(), map[string]any{ - "id": id, + token, err := jwtx.CreateToken(c.Context(), jwtx.TokenData{ + UserID: id, }) if err != nil { return errors.WithStack(err) @@ -129,25 +129,24 @@ func registerUser( err := dbClient.WithTx(c.Context(), func(tx *db.Client) error { - if err := tx.Insert(c.Context(), "user", "id", map[string]any{ + if err := tx.Insert(c.Context(), "b_user", "id", map[string]any{ "id": userId, "nickname": nickname, "avatar": avatar, - "status": 1, - "create_time": "NOW()", - "update_time": "NOW()", + "created_at": "NOW()", + "updated_at": "NOW()", "last_login_time": "NOW()", }); err != nil { return err } - if err := tx.Insert(c.Context(), "user_oauth", "id", map[string]any{ - "id": uniqueid.NextId(), - "user_id": userId, - "type": loginType, - "openid": openid, - "create_time": "NOW()", - "update_time": "NOW()", + if err := tx.Insert(c.Context(), "b_user_oauth", "id", map[string]any{ + "id": uniqueid.NextId(), + "user_id": userId, + "type": loginType, + "openid": openid, + "created_at": "NOW()", + "updated_at": "NOW()", }); err != nil { return err } @@ -159,8 +158,8 @@ func registerUser( return errors.WithStack(err) } - token, err := jwtx.CreateToken(c.Context(), map[string]any{ - "id": userId, + token, err := jwtx.CreateToken(c.Context(), jwtx.TokenData{ + UserID: userId, }) if err != nil { return errors.WithStack(err) @@ -179,7 +178,10 @@ func getUserByOpenID(dbClient *db.Client, c fiber.Ctx, openid string, loginType users, err := dbClient.LoadDataBySQL( c.Context(), - "SELECT u.* FROM user_oauth o JOIN user u ON u.id = o.user_id WHERE o.openid = ? AND o.type = ?", + `SELECT u.* + FROM b_user_oauth o + JOIN b_user u ON u.id = o.user_id + WHERE o.openid = $1 AND o.type = $2`, []any{openid, loginType}, ) @@ -196,6 +198,19 @@ func getUserByOpenID(dbClient *db.Client, c fiber.Ctx, openid string, loginType // ======================== 类型工具(极简版) ======================== func toInt64(v any) int64 { - i, _ := v.(int64) - return i + switch val := v.(type) { + case int64: + return val + case int32: + return int64(val) + case int16: + return int64(val) + case int: + return int64(val) + case float64: + return int64(val) + default: + fmt.Printf("unknown type: %T, value=%v\n", v, v) + return 0 + } } diff --git a/code/allapp/allapp-go-v3/internal/handle/s3.go b/code/allapp/allapp-go-v3/internal/handle/s3.go new file mode 100644 index 00000000..40462092 --- /dev/null +++ b/code/allapp/allapp-go-v3/internal/handle/s3.go @@ -0,0 +1,79 @@ +package handle + +import ( + "allapp-go/internal/errors" + "allapp-go/internal/httpx" + "allapp-go/pkg/s3store" + "fmt" + "mime/multipart" + "path/filepath" + "time" + + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/gofiber/fiber/v3" + "github.com/google/uuid" +) + +func S3ListBuckets(c fiber.Ctx) error { + result, err := s3store.S3Client.ListBuckets(c.Context(), &s3.ListBucketsInput{}) + if err != nil { + return errors.WithStack(err) + } + + buckets := make([]string, 0, len(result.Buckets)) + for _, b := range result.Buckets { + if b.Name != nil { + buckets = append(buckets, *b.Name) + } + } + + return httpx.OK(c, buckets) +} + +func S3UploadFile(c fiber.Ctx) error { + fileHeader, err := c.FormFile("file") + if err != nil { + return errors.WithStack(err) + } + + file, err := fileHeader.Open() + if err != nil { + return errors.WithStack(err) + } + defer func(file multipart.File) { + _ = file.Close() + }(file) + + // 日期目录 + now := time.Now() + folderPath := fmt.Sprintf("%d/%02d/%02d", + now.Year(), + now.Month(), + now.Day(), + ) + + // UUID 文件名(保留后缀) + ext := filepath.Ext(fileHeader.Filename) + newName := fmt.Sprintf("%s%s", uuid.NewString(), ext) + + // S3 object key + key := fmt.Sprintf("%s/%s", folderPath, newName) + + // ContentType(防止前端乱传) + contentType := fileHeader.Header.Get("Content-Type") + + // 调用存储层 + err = s3store.UploadToRustFS( + c.Context(), + key, + file, + fileHeader.Size, + contentType, + ) + + if err != nil { + return errors.WithStack(err) + } + + return httpx.OK(c, nil) +} diff --git a/code/allapp/allapp-go-v3/internal/middleware/auth.go b/code/allapp/allapp-go-v3/internal/middleware/auth.go index b453dbb9..d4bb557e 100644 --- a/code/allapp/allapp-go-v3/internal/middleware/auth.go +++ b/code/allapp/allapp-go-v3/internal/middleware/auth.go @@ -1,7 +1,10 @@ package middleware import ( + "allapp-go/internal/httpx" + "allapp-go/pkg/jwtx" "context" + "strings" "github.com/gofiber/fiber/v3" ) @@ -11,20 +14,18 @@ const CtxUserIDKey = "user_id" // Auth 登录校验中间件 func Auth() fiber.Handler { return func(c fiber.Ctx) error { - //token := strings.TrimSpace(c.Get("Token")) - // - //if token == "" { - // return httpx.Unauthorized(c, "未登录") - //} - // - //verifyToken, ok := jwtx.VerifyToken(c.Context(), token) - //if !ok { - // return httpx.Unauthorized(c, "登录已过期") - //} - // - //c.Locals(CtxUserIDKey, verifyToken.Data["user_id"]) + token := strings.TrimSpace(c.Get("Token")) - ctx := context.WithValue(c.Context(), CtxUserIDKey, int64(779895419121733)) + if token == "" { + return httpx.Unauthorized(c, "未登录") + } + + verifyToken, ok := jwtx.VerifyToken(c.Context(), token) + if !ok { + return httpx.Unauthorized(c, "登录已过期") + } + + ctx := context.WithValue(c.Context(), CtxUserIDKey, verifyToken.Data.UserID) c.SetContext(ctx) return c.Next() diff --git a/code/allapp/allapp-go-v3/internal/router/router.go b/code/allapp/allapp-go-v3/internal/router/router.go index 5b9d9f8b..fbd91f12 100644 --- a/code/allapp/allapp-go-v3/internal/router/router.go +++ b/code/allapp/allapp-go-v3/internal/router/router.go @@ -19,6 +19,10 @@ func SetupRouter(app *fiber.App, cfg *config.Config) { data.Post("/saveData", handle.SaveData) data.Post("/getUniqueId", handle.GetUniqueId) + s3 := api.Group("/s3", middleware.Auth()) + s3.Post("/listBuckets", handle.S3ListBuckets) + s3.Post("/upload", handle.S3UploadFile) + // ==================== auth ==================== api.Post("/auth/login/qq", handle.LoginQq) api.Post("/auth/login/wechat", handle.LoginWechat) diff --git a/code/allapp/allapp-go-v3/pkg/db/common.go b/code/allapp/allapp-go-v3/pkg/db/common.go index 8672148d..9f5d12ab 100644 --- a/code/allapp/allapp-go-v3/pkg/db/common.go +++ b/code/allapp/allapp-go-v3/pkg/db/common.go @@ -7,8 +7,8 @@ import ( ) var auditExcludeTables = map[string]bool{ - "user": true, - "user_oauth": true, + "b_user": true, + "b_user_oauth": true, } func validateData(table string, keyField string, data map[string]any) (any, error) { diff --git a/code/allapp/allapp-go-v3/pkg/db/delete.go b/code/allapp/allapp-go-v3/pkg/db/delete.go index a74bd51f..788b8d52 100644 --- a/code/allapp/allapp-go-v3/pkg/db/delete.go +++ b/code/allapp/allapp-go-v3/pkg/db/delete.go @@ -16,11 +16,21 @@ func (c *Client) Delete( return fmt.Errorf("keyField 不能为空") } - keyVal, err := validateData(table, keyField, data) + rawTable := table + + keyVal, err := validateData(rawTable, keyField, data) if err != nil { return err } + tableParts := strings.Split(table, ".") + for i, p := range tableParts { + tableParts[i] = `"` + p + `"` + } + table = strings.Join(tableParts, ".") + + keyField = `"` + keyField + `"` + sql := fmt.Sprintf( "DELETE FROM %s WHERE %s = $1", table, @@ -50,6 +60,16 @@ func (c *Client) BatchDelete( return fmt.Errorf("数据不能为空") } + rawTable := table + + tableParts := strings.Split(table, ".") + for i, p := range tableParts { + tableParts[i] = `"` + p + `"` + } + table = strings.Join(tableParts, ".") + + keyFieldQuoted := `"` + keyField + `"` + var ( placeholders []string args []any @@ -57,7 +77,7 @@ func (c *Client) BatchDelete( ) for _, data := range dataList { - keyVal, err := validateData(table, keyField, data) + keyVal, err := validateData(rawTable, keyField, data) if err != nil { return err } @@ -74,7 +94,7 @@ func (c *Client) BatchDelete( sql := fmt.Sprintf( "DELETE FROM %s WHERE %s IN (%s)", table, - keyField, + keyFieldQuoted, strings.Join(placeholders, ", "), ) diff --git a/code/allapp/allapp-go-v3/pkg/db/insert.go b/code/allapp/allapp-go-v3/pkg/db/insert.go index 484488bd..9e9b8f06 100644 --- a/code/allapp/allapp-go-v3/pkg/db/insert.go +++ b/code/allapp/allapp-go-v3/pkg/db/insert.go @@ -17,9 +17,18 @@ func buildInsertSQL(table string, data map[string]any) (string, []any) { args []any ) + // 处理 schema.table 的情况 + tableParts := strings.Split(table, ".") + for i, p := range tableParts { + tableParts[i] = `"` + p + `"` + } + table = strings.Join(tableParts, ".") + i := 1 for col, val := range data { - columns = append(columns, col) + // 列名加引号 + columns = append(columns, `"`+col+`"`) + placeholders = append(placeholders, fmt.Sprintf("$%d", i)) args = append(args, val) i++ @@ -80,12 +89,18 @@ func (c *Client) BatchInsert( return fmt.Errorf("数据不能为空") } - // 用第一条确定列顺序 first := dataList[0] if _, err := validateData(table, keyField, first); err != nil { return err } + // 👉 表名加引号(支持 schema) + tableParts := strings.Split(table, ".") + for i, p := range tableParts { + tableParts[i] = `"` + p + `"` + } + table = strings.Join(tableParts, ".") + var columns []string for col := range first { columns = append(columns, col) @@ -93,6 +108,12 @@ func (c *Client) BatchInsert( sort.Strings(columns) + // 👉 列名加引号 + var quotedColumns []string + for _, col := range columns { + quotedColumns = append(quotedColumns, `"`+col+`"`) + } + var ( valueStrings []string args []any @@ -100,12 +121,10 @@ func (c *Client) BatchInsert( ) for _, data := range dataList { - // 统一校验 if _, err := validateData(table, keyField, data); err != nil { return err } - // 字段数量检查 if len(data) != len(columns) { return fmt.Errorf("批量插入失败:数据字段不一致") } @@ -132,7 +151,7 @@ func (c *Client) BatchInsert( sql := fmt.Sprintf( "INSERT INTO %s (%s) VALUES %s", table, - strings.Join(columns, ", "), + strings.Join(quotedColumns, ", "), strings.Join(valueStrings, ", "), ) diff --git a/code/allapp/allapp-go-v3/pkg/db/update.go b/code/allapp/allapp-go-v3/pkg/db/update.go index 2d725646..35b3ec85 100644 --- a/code/allapp/allapp-go-v3/pkg/db/update.go +++ b/code/allapp/allapp-go-v3/pkg/db/update.go @@ -29,12 +29,21 @@ func (c *Client) Update( data["update_time"] = now userID, flag := middleware.GetUserID(ctx) - if flag { data["update_by"] = userID } } + // 👉 表名加引号(支持 schema) + tableParts := strings.Split(table, ".") + for i, p := range tableParts { + tableParts[i] = `"` + p + `"` + } + table = strings.Join(tableParts, ".") + + // 👉 keyField 加引号 + keyField = `"` + keyField + `"` + var ( setClauses []string args []any @@ -45,12 +54,15 @@ func (c *Client) Update( if col == keyField { continue } - setClauses = append(setClauses, fmt.Sprintf("%s=$%d", col, i)) + + // 👉 列名加引号 + colQuoted := `"` + col + `"` + + setClauses = append(setClauses, fmt.Sprintf("%s=$%d", colQuoted, i)) args = append(args, val) i++ } - // WHERE 条件 where := fmt.Sprintf("%s=$%d", keyField, i) args = append(args, keyVal) @@ -76,13 +88,21 @@ func (c *Client) BatchUpdate( return fmt.Errorf("数据不能为空") } - // 用第一条数据确定字段 first := dataList[0] if _, err := validateData(table, keyField, first); err != nil { return err } - // 提取字段(排除主键)+ 排序(关键) + // 👉 表名加引号(支持 schema) + tableParts := strings.Split(table, ".") + for i, p := range tableParts { + tableParts[i] = `"` + p + `"` + } + table = strings.Join(tableParts, ".") + + // 👉 keyField 加引号 + keyFieldQuoted := `"` + keyField + `"` + var columns []string for col := range first { if col != keyField { @@ -96,16 +116,15 @@ func (c *Client) BatchUpdate( argIndex = 1 ) - // CASE 语句 var setClauses []string for _, col := range columns { - var caseBuilder strings.Builder + colQuoted := `"` + col + `"` - caseBuilder.WriteString(fmt.Sprintf("%s = CASE %s ", col, keyField)) + var caseBuilder strings.Builder + caseBuilder.WriteString(fmt.Sprintf("%s = CASE %s ", colQuoted, keyFieldQuoted)) for _, data := range dataList { - // 校验 key keyVal, err := validateData(table, keyField, data) if err != nil { return err @@ -130,7 +149,6 @@ func (c *Client) BatchUpdate( setClauses = append(setClauses, caseBuilder.String()) } - // WHERE IN var wherePlaceholders []string for _, data := range dataList { @@ -148,7 +166,7 @@ func (c *Client) BatchUpdate( "UPDATE %s SET %s WHERE %s IN (%s)", table, strings.Join(setClauses, ", "), - keyField, + keyFieldQuoted, strings.Join(wherePlaceholders, ", "), ) diff --git a/code/allapp/allapp-go-v3/pkg/jwtx/jwtx.go b/code/allapp/allapp-go-v3/pkg/jwtx/jwtx.go index 83c1d46f..bdda844e 100644 --- a/code/allapp/allapp-go-v3/pkg/jwtx/jwtx.go +++ b/code/allapp/allapp-go-v3/pkg/jwtx/jwtx.go @@ -48,8 +48,12 @@ func getManager() *jwtManager { // Claims(通用结构) // ================== +type TokenData struct { + UserID int64 `json:"user_id"` +} + type CustomClaims struct { - Data map[string]any `json:"data"` + Data TokenData `json:"data"` jwt.RegisteredClaims } @@ -58,7 +62,7 @@ type CustomClaims struct { // ================== // CreateToken 生成 JWT -func CreateToken(ctx context.Context, data map[string]any) (string, error) { +func CreateToken(ctx context.Context, data TokenData) (string, error) { m := getManager() return m.createToken(data) } @@ -91,7 +95,7 @@ func VerifyToken(ctx context.Context, tokenString string) (*CustomClaims, bool) // 内部实现 // ================== -func (j *jwtManager) createToken(data map[string]any) (string, error) { +func (j *jwtManager) createToken(data TokenData) (string, error) { now := time.Now() claims := &CustomClaims{ @@ -101,8 +105,6 @@ func (j *jwtManager) createToken(data map[string]any) (string, error) { }, } - // ✅ 只有 >0 才设置过期时间 - // -1 / 0 都表示不过期 if j.tokenExpiry > 0 { claims.ExpiresAt = jwt.NewNumericDate(now.Add(j.tokenExpiry)) } diff --git a/code/allapp/allapp-go-v3/pkg/s3store/index.go b/code/allapp/allapp-go-v3/pkg/s3store/index.go new file mode 100644 index 00000000..4e610442 --- /dev/null +++ b/code/allapp/allapp-go-v3/pkg/s3store/index.go @@ -0,0 +1,52 @@ +package s3store + +import ( + "allapp-go/internal/config" + "context" + "fmt" + "mime/multipart" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +var S3Client *s3.Client +var presignClient *s3.PresignClient +var bucket string + +func NewS3Client(cfg *config.Config) { + credProvider := credentials.NewStaticCredentialsProvider( + cfg.AwsS3.AccessKey, + cfg.AwsS3.SecretKey, + "", + ) + + awsCfg := aws.Config{ + Region: cfg.AwsS3.Region, + Credentials: credProvider, + } + + S3Client = s3.NewFromConfig(awsCfg, func(o *s3.Options) { + o.BaseEndpoint = aws.String(cfg.AwsS3.Endpoint) + o.UsePathStyle = true + }) + + presignClient = s3.NewPresignClient(S3Client) + + bucket = cfg.AwsS3.Bucket + + fmt.Println("S3 client and Presign client initialized successfully!") +} + +func UploadToRustFS(ctx context.Context, key string, file multipart.File, size int64, contentType string) error { + _, err := S3Client.PutObject(ctx, &s3.PutObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + Body: file, + ContentLength: aws.Int64(size), + ContentType: aws.String(contentType), + }) + + return err +} diff --git a/code/app/app-rn4/app.json b/code/app/app-rn4/app.json index 9da75177..3b18eeaf 100644 --- a/code/app/app-rn4/app.json +++ b/code/app/app-rn4/app.json @@ -6,11 +6,10 @@ "orientation": "portrait", "icon": "./assets/images/icon.png", "userInterfaceStyle": "automatic", - "scheme": [ - "oneao", - "tencent102826474", - "wxdab3e21a1f7e392f" - ], + "scheme": ["oneao", "tencent102826474", "wxdab3e21a1f7e392f"], + "extra": { + "linkingScheme": "oneao" + }, "ios": { "icon": "./assets/expo.icon" }, @@ -58,7 +57,27 @@ } } ], - "expo-wechat" + "expo-wechat", + [ + "expo-document-picker", + { + "iCloudContainerEnvironment": "Production" + } + ], + [ + "expo-image-picker", + { + "photosPermission": "The app accesses your photos to let you share them with your friends.", + "colors": { + "cropToolbarColor": "#000000" + }, + "dark": { + "colors": { + "cropToolbarColor": "#000000" + } + } + } + ] ], "experiments": { "typedRoutes": true, diff --git a/code/app/app-rn4/package.json b/code/app/app-rn4/package.json index 0201a24b..56cc2c0f 100644 --- a/code/app/app-rn4/package.json +++ b/code/app/app-rn4/package.json @@ -27,9 +27,11 @@ "expo-build-properties": "~55.0.13", "expo-constants": "~55.0.14", "expo-device": "~55.0.15", + "expo-document-picker": "~55.0.13", "expo-font": "~55.0.6", "expo-glass-effect": "~55.0.10", "expo-image": "~55.0.8", + "expo-image-picker": "~55.0.18", "expo-linear-gradient": "~55.0.13", "expo-linking": "~55.0.13", "expo-qq": "^0.1.8", diff --git a/code/app/app-rn4/plugins/withAndroidSignature.js b/code/app/app-rn4/plugins/withAndroidSignature.js index cc48d6fe..61af8bad 100644 --- a/code/app/app-rn4/plugins/withAndroidSignature.js +++ b/code/app/app-rn4/plugins/withAndroidSignature.js @@ -1,95 +1,47 @@ -const fs = require("node:fs"); -const path = require("node:path"); -const { withAppBuildGradle } = require("expo/config-plugins"); +// plugins/withAndroidSignature.js -const CREDENTIAL_PATH = path.resolve( - __dirname, - "../credentials/credentials.json" -); - -const KEYSTORE_PATH = path.resolve( - __dirname, - "../credentials/oneao-android.keystore" -); +const fs = require('node:fs') +const path = require('node:path') +const { withAppBuildGradle } = require('expo/config-plugins') module.exports = function withAndroidSignature(config) { return withAppBuildGradle(config, (config) => { - if (config.modResults.language !== "groovy") { - throw new Error("only groovy build.gradle is supported"); + if (config.modResults.language === 'groovy') { + config.modResults.contents = setAndroidSignature(config.modResults.contents) } + else { + throw new Error('如果不是 groovy,则无法在 app/build.gradle 中设置 signingConfigs') + } + return config + }) +} - config.modResults.contents = setAndroidSignature( - config.modResults.contents - ); - - return config; - }); -}; - -// ======================== 主入口 ======================== function setAndroidSignature(appBuildGradle) { - if (!fs.existsSync(CREDENTIAL_PATH)) { - console.warn("⚠️ credentials.json 不存在,跳过签名配置"); - return appBuildGradle; + if (!fs.existsSync(path.resolve(__dirname, '../credentials/credentials.json'))) { + console.warn('警告:没有设置正式版本的 Android Keystore 文件,因为 credentials.json 不存在。') + return appBuildGradle } + const info = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../credentials/credentials.json'), { encoding: 'utf8' })) - const info = JSON.parse( - fs.readFileSync(CREDENTIAL_PATH, "utf8") - ); - - let output = appBuildGradle; - - // ⭐ 1. 重写 signingConfigs(关键) - output = replaceSigningConfigs(output, info); - - // ⭐ 2. 确保所有 debug/release 使用统一 keystore - output = unifySigningUsage(output); - - return output; -} - -// ======================== ⭐核心:重写 signingConfigs ======================== -function replaceSigningConfigs(appBuildGradle, info) { - const block = ` - signingConfigs { + // 使用正则表达式插入签名信息 + let output = appBuildGradle.replace( + /(signingConfigs\s*\{)/, + `$1 release { - storeFile file(${JSON.stringify(KEYSTORE_PATH)}) + storeFile file(${JSON.stringify(path.resolve(__dirname, '../credentials/oneao-android.keystore'))}) storePassword ${JSON.stringify(info.android.keystore.keystorePassword)} keyAlias ${JSON.stringify(info.android.keystore.keyAlias)} keyPassword ${JSON.stringify(info.android.keystore.keyPassword)} - } + }`, + ) - debug { - storeFile file(${JSON.stringify(KEYSTORE_PATH)}) - storePassword ${JSON.stringify(info.android.keystore.keystorePassword)} - keyAlias ${JSON.stringify(info.android.keystore.keyAlias)} - keyPassword ${JSON.stringify(info.android.keystore.keyPassword)} - } - } - `; - - const regex = /signingConfigs\s*\{[\s\S]*?\n\s*\}/m; - - if (regex.test(appBuildGradle)) { - return appBuildGradle.replace(regex, block.trim()); - } - - // 没有就插入 - return appBuildGradle.replace( - /(android\s*\{)/, - `$1\n${block.trim()}` - ); -} - -// ======================== ⭐统一使用 signingConfig ======================== -function unifySigningUsage(appBuildGradle) { - let output = appBuildGradle; - - // debug -> release(你如果想 debug 也用 release,可以改这里) + // 使用正则表达式替换 signingConfig output = output.replace( - /signingConfig\s+signingConfigs\.debug/g, - "signingConfig signingConfigs.release" - ); + /(release\s*\{)[^}]*?signingConfig\s+signingConfigs\.debug/, + `$1 + signingConfig signingConfigs.release +`, + ) - return output; -} \ No newline at end of file + return output +} diff --git a/code/app/app-rn4/pnpm-lock.yaml b/code/app/app-rn4/pnpm-lock.yaml index dff31067..ad040fde 100644 --- a/code/app/app-rn4/pnpm-lock.yaml +++ b/code/app/app-rn4/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: expo-device: specifier: ~55.0.15 version: 55.0.15(expo@55.0.15) + expo-document-picker: + specifier: ~55.0.13 + version: 55.0.13(expo@55.0.15) expo-font: specifier: ~55.0.6 version: 55.0.6(expo@55.0.15)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) @@ -59,6 +62,9 @@ importers: expo-image: specifier: ~55.0.8 version: 55.0.8(expo@55.0.15)(react-native-web@0.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + expo-image-picker: + specifier: ~55.0.18 + version: 55.0.18(expo@55.0.15) expo-linear-gradient: specifier: ~55.0.13 version: 55.0.13(expo@55.0.15)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) @@ -1010,56 +1016,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-arm64-musl@0.44.0': resolution: {integrity: sha512-AlObQIXyVRZ96LbtVljtFq0JqH5B92NU+BQeDFrXWBUWlCKAM0wF5GLfIhCLT5kQ3Sl+U0YjRJ7Alqj5hGQaCg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-ppc64-gnu@0.44.0': resolution: {integrity: sha512-YcFE8/q/BbrCiIiM5piwbkA6GwJc5QqhMQp2yDrqQ2fuVkZ7CInb1aIijZ/k8EXc72qXMSwKpVlBv1w/MsGO/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-gnu@0.44.0': resolution: {integrity: sha512-eOdzs6RqkRzuqNHUX5C8ISN5xfGh4xDww8OEd9YAmc3OWN8oAe5bmlIqQ+rrHLpv58/0BuU48bxkhnIGjA/ATQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-musl@0.44.0': resolution: {integrity: sha512-YBgNTxntD/QvlFUfgvh8bEdwOhXiquX8gaofZJAwYa/Xp1S1DQrFVZEeck7GFktr24DztsSp8N8WtWCBwxs0Hw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-s390x-gnu@0.44.0': resolution: {integrity: sha512-GLIh1R6WHWshl/i4QQDNgj0WtT25aRO4HNUWEoitxiywyRdhTFmFEYT2rXlcl9U6/26vhmOqG5cRlMLG3ocaIA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-gnu@0.44.0': resolution: {integrity: sha512-gZOpgTlOsLcLfAF9qgpTr7FIIFSKnQN3hDf/0JvQ4CIwMY7h+eilNjxq/CorqvYcEOu+LRt1W4ZS7KccEHLOdA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-musl@0.44.0': resolution: {integrity: sha512-1CyS9JTB+pCUFYFI6pkQGGZaT/AY5gnhHVrQQLhFba6idP9AzVYm1xbdWfywoldTYvjxQJV6x4SuduCIfP3W+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxfmt/binding-openharmony-arm64@0.44.0': resolution: {integrity: sha512-bmEv70Ak6jLr1xotCbF5TxIKjsmQaiX+jFRtnGtfA03tJPf6VG3cKh96S21boAt3JZc+Vjx8PYcDuLj39vM2Pw==} @@ -1132,56 +1130,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-arm64-musl@1.60.0': resolution: {integrity: sha512-eDYDXZGhQAXyn6GwtwiX/qcLS0HlOLPJ/+iiIY8RYr+3P8oKBmgKxADLlniL6FtWfE7pPk7IGN9/xvDEvDvFeg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxlint/binding-linux-ppc64-gnu@1.60.0': resolution: {integrity: sha512-nxehly5XYBHUWI9VJX1bqCf9j/B43DaK/aS/T1fcxCpX3PA4Rm9BB54nPD1CKayT8xg6REN1ao+01hSRNgy8OA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-gnu@1.60.0': resolution: {integrity: sha512-j1qf/NaUfOWQutjeoooNG1Q0zsK0XGmSu1uDLq3cctquRF3j7t9Hxqf/76ehCc5GEUAanth2W4Fa+XT1RFg/nw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-musl@1.60.0': resolution: {integrity: sha512-YELKPRefQ/q/h3RUmeRfPCUhh2wBvgV1RyZ/F9M9u8cDyXsQW2ojv1DeWQTt466yczDITjZnIOg/s05pk7Ve2A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxlint/binding-linux-s390x-gnu@1.60.0': resolution: {integrity: sha512-JkO3C6Gki7Y6h/MiIkFKvHFOz98/YWvQ4WYbK9DLXACMP2rjULzkeGyAzorJE5S1dzLQGFgeqvN779kSFwoV1g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-gnu@1.60.0': resolution: {integrity: sha512-XjKHdFVCpZZZSWBCKyyqCq65s2AKXykMXkjLoKYODrD+f5toLhlwsMESscu8FbgnJQ4Y/dpR/zdazsahmgBJIA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-musl@1.60.0': resolution: {integrity: sha512-js29ZWIuPhNWzY8NC7KoffEMEeWG105vbmm+8EOJsC+T/jHBiKIJEUF78+F/IrgEWMMP9N0kRND4Pp75+xAhKg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxlint/binding-openharmony-arm64@1.60.0': resolution: {integrity: sha512-H+PUITKHk04stFpWj3x3Kg08Afp/bcXSBi0EhasR5a0Vw7StXHTzdl655PUI0fB4qdh2Wsu6Dsi+3ACxPoyQnA==} @@ -1600,28 +1590,24 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -2256,6 +2242,11 @@ packages: peerDependencies: expo: '*' + expo-document-picker@55.0.13: + resolution: {integrity: sha512-IhswJElhdzs3fKDEKW8KXYRoFkWGEsXRMYAZT46Yo56zqqy8yQXrczo33RSwD2hFzNQBdLT97SJL9N311UyS3g==} + peerDependencies: + expo: '*' + expo-file-system@55.0.16: resolution: {integrity: sha512-EetQ/zVFK07Vmz4Yke0fvoES4xVwScTdd0PMoLekuMX7puE4op75pNnEdh1M0AeWzkqLrBoZIaU2ynSrKN5VZg==} peerDependencies: @@ -2276,6 +2267,16 @@ packages: react: '*' react-native: '*' + expo-image-loader@55.0.0: + resolution: {integrity: sha512-NOjp56wDrfuA5aiNAybBIjqIn1IxKeGJ8CECWZncQ/GzjZfyTYAHTCyeApYkdKkMBLHINzI4BbTGSlbCa0fXXQ==} + peerDependencies: + expo: '*' + + expo-image-picker@55.0.18: + resolution: {integrity: sha512-lGpPGRu+7mE8qN0ma2boRsCmfOGbdHZ2bXTpWVeWly0JCZdogGlTrYFnhTqgS8+lmiRb/UCOs7iTm2P5Rra6kw==} + peerDependencies: + expo: '*' + expo-image@55.0.8: resolution: {integrity: sha512-fNdvdYVcGn3g1x6o5AXHKzk4xX8U6rg2W9vFdE1pQO80kWCNReh003ypqSrGy4dD+zA8FtZjrNF3oMDGnPpIGQ==} peerDependencies: @@ -2539,7 +2540,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} @@ -2872,84 +2873,72 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-gnu@1.31.1: resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.1: resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.1: resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.1: resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.1: resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} @@ -6497,6 +6486,10 @@ snapshots: expo: 55.0.15(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.9)(expo-router@55.0.12)(react-dom@19.2.0(react@19.2.0))(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) ua-parser-js: 0.7.41 + expo-document-picker@55.0.13(expo@55.0.15): + dependencies: + expo: 55.0.15(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.9)(expo-router@55.0.12)(react-dom@19.2.0(react@19.2.0))(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-file-system@55.0.16(expo@55.0.15)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)): dependencies: expo: 55.0.15(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.9)(expo-router@55.0.12)(react-dom@19.2.0(react@19.2.0))(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) @@ -6515,6 +6508,15 @@ snapshots: react: 19.2.0 react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) + expo-image-loader@55.0.0(expo@55.0.15): + dependencies: + expo: 55.0.15(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.9)(expo-router@55.0.12)(react-dom@19.2.0(react@19.2.0))(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + + expo-image-picker@55.0.18(expo@55.0.15): + dependencies: + expo: 55.0.15(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.9)(expo-router@55.0.12)(react-dom@19.2.0(react@19.2.0))(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-image-loader: 55.0.0(expo@55.0.15) + expo-image@55.0.8(expo@55.0.15)(react-native-web@0.21.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): dependencies: expo: 55.0.15(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.9)(expo-router@55.0.12)(react-dom@19.2.0(react@19.2.0))(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) diff --git a/code/app/app-rn4/src/app/(tabs)/chat/BottomSheetKeyboardAwareScrollView.tsx b/code/app/app-rn4/src/app/(tabs)/chat/BottomSheetKeyboardAwareScrollView.tsx new file mode 100644 index 00000000..ca429934 --- /dev/null +++ b/code/app/app-rn4/src/app/(tabs)/chat/BottomSheetKeyboardAwareScrollView.tsx @@ -0,0 +1,25 @@ +import { memo } from "react"; +import { + KeyboardAwareScrollView, + KeyboardAwareScrollViewProps, +} from "react-native-keyboard-controller"; +import { + SCROLLABLE_TYPE, + createBottomSheetScrollableComponent, + type BottomSheetScrollViewMethods, +} from "@gorhom/bottom-sheet"; +import type { BottomSheetScrollViewProps } from "@gorhom/bottom-sheet/src/components/bottomSheetScrollable/types"; +import Reanimated from "react-native-reanimated"; + +const AnimatedScrollView = Reanimated.createAnimatedComponent(KeyboardAwareScrollView); +const BottomSheetScrollViewComponent = createBottomSheetScrollableComponent< + BottomSheetScrollViewMethods, + BottomSheetScrollViewProps +>(SCROLLABLE_TYPE.SCROLLVIEW, AnimatedScrollView); +const BottomSheetKeyboardAwareScrollView = memo(BottomSheetScrollViewComponent); + +BottomSheetKeyboardAwareScrollView.displayName = "BottomSheetKeyboardAwareScrollView"; + +export default BottomSheetKeyboardAwareScrollView as ( + props: BottomSheetScrollViewProps & KeyboardAwareScrollViewProps, +) => ReturnType; diff --git a/code/app/app-rn4/src/app/(tabs)/chat/index.tsx b/code/app/app-rn4/src/app/(tabs)/chat/index.tsx index 30eff9c6..399b7b91 100644 --- a/code/app/app-rn4/src/app/(tabs)/chat/index.tsx +++ b/code/app/app-rn4/src/app/(tabs)/chat/index.tsx @@ -1,43 +1,54 @@ -import { Button, Toast, useToast, useThemeColor } from "heroui-native"; -import { View } from "react-native"; +import { BottomSheet, Button, TextField, Input, useBottomSheetAwareHandlers } from "heroui-native"; +import { useState, useRef, useEffect } from "react"; +import { Keyboard } from "react-native"; -export default function ToastExample() { - const { toast } = useToast(); - const themeColorForeground = useThemeColor("foreground"); +const BottomSheetTextInput = ({ isOpen }: { isOpen: boolean }) => { + const { onFocus, onBlur } = useBottomSheetAwareHandlers(); + const inputRef = useRef(null); + + useEffect(() => { + if (isOpen) { + inputRef.current?.focus(); + } else { + inputRef.current?.blur(); + } + }, [isOpen]); return ( - - + + + + ); +}; - - + }} + animation="disable-all" + > + + + + + + + + + + + + ); } diff --git a/code/app/app-rn4/src/app/(tabs)/chat/index2.tsx b/code/app/app-rn4/src/app/(tabs)/chat/index2.tsx new file mode 100644 index 00000000..259aeb19 --- /dev/null +++ b/code/app/app-rn4/src/app/(tabs)/chat/index2.tsx @@ -0,0 +1,39 @@ +import { BottomSheet, Button, TextField, Input, useBottomSheetAwareHandlers } from "heroui-native"; +import { useState } from "react"; + +const BottomSheetTextInput = () => { + const { onFocus, onBlur } = useBottomSheetAwareHandlers(); + + return ( + + + + ); +}; + +export default function BottomSheetExample() { + const [isOpen, setIsOpen] = useState(false); + + return ( + { + setIsOpen(open); + }} + > + + + + + + + + + + + + + ); +} diff --git a/code/app/app-rn4/src/app/(tabs)/home/index.tsx b/code/app/app-rn4/src/app/(tabs)/home/index.tsx index a8935bf6..f583c0b3 100644 --- a/code/app/app-rn4/src/app/(tabs)/home/index.tsx +++ b/code/app/app-rn4/src/app/(tabs)/home/index.tsx @@ -1,6 +1,8 @@ -import React from "react"; +import React, { useEffect } from "react"; import LayoutView from "@/components/layout/view"; import EmptyState from "@/components/EmptyStatus"; +import { s3ListBucketsApi } from "@/request/api"; + export default function Home() { // const [dailyData, setDailyData] = useState(null); @@ -15,6 +17,12 @@ export default function Home() { // .catch(() => {}); // }, []); + useEffect(() => { + s3ListBucketsApi().then((res) => { + console.log(res); + }); + }, []); + return ( diff --git a/code/app/app-rn4/src/app/(tabs)/my/index.tsx b/code/app/app-rn4/src/app/(tabs)/my/index.tsx index 79ab74c5..bb9b8067 100644 --- a/code/app/app-rn4/src/app/(tabs)/my/index.tsx +++ b/code/app/app-rn4/src/app/(tabs)/my/index.tsx @@ -30,12 +30,14 @@ export default function My() { - - JD + + {userStore.userInfo.nickname} - JD + + {userStore.userInfo.nickname} + diff --git a/code/app/app-rn4/src/app/(tabs)/space/index.tsx b/code/app/app-rn4/src/app/(tabs)/space/index.tsx index 1fc6193a..28a303ab 100644 --- a/code/app/app-rn4/src/app/(tabs)/space/index.tsx +++ b/code/app/app-rn4/src/app/(tabs)/space/index.tsx @@ -26,6 +26,48 @@ import { UserRoundIcon, UsersRoundIcon } from "lucide-react-native"; import { toast } from "sonner-native"; import { FadeIn } from "react-native-reanimated"; import { generateCode } from "@/utils/common"; +import { Keyboard } from "react-native"; +import { forwardRef } from "react"; + +const BottomSheetTextInput = forwardRef>((props, ref) => { + const { onFocus, onBlur } = useBottomSheetAwareHandlers(); + + return ( + { + onFocus(e); + props.onFocus?.(e); + }} + onBlur={(e) => { + onBlur(e); + props.onBlur?.(e); + }} + /> + ); +}); + +const BottomSheetTextInputOTP = forwardRef>( + (props, ref) => { + const { onFocus, onBlur } = useBottomSheetAwareHandlers(); + + return ( + { + onFocus(e); + props.onFocus?.(e); + }} + onBlur={(e) => { + onBlur(e); + props.onBlur?.(e); + }} + /> + ); + }, +); export default function Space() { const router = useRouter(); @@ -54,28 +96,38 @@ export default function Space() { const [createLoading, setCreateLoading] = useState(false); const createInputRef = useRef(null); - const { onFocus, onBlur } = useBottomSheetAwareHandlers(); + useEffect(() => { + listUserSpaces(); + }, []); useEffect(() => { if (showCreate) { - setTimeout(() => { + requestAnimationFrame(() => { createInputRef.current?.focus(); - }, 0); + }); + } else { + setCreateForm({ + name: "", + }); + setCreateLoading(false); + Keyboard.dismiss(); } }, [showCreate]); useEffect(() => { if (showJoin) { - setTimeout(() => { + requestAnimationFrame(() => { joinInputRef.current?.focus(); - }, 0); + }); + } else { + setJoinForm({ + invite_code: "", + }); + setJoinLoading(false); + Keyboard.dismiss(); } }, [showJoin]); - useEffect(() => { - listUserSpaces(); - }, []); - function listUserSpaces(loading = true) { if (loading) { setLoading(true); @@ -396,7 +448,7 @@ export default function Space() { className="flex-1" onPress={() => { setShowCreate(true); - createInputRef.current?.focus(); + // createInputRef.current?.focus(); }} > @@ -463,32 +515,25 @@ export default function Space() { /> )} - { - if (!val) { - setCreateForm({ - name: "", - }); - setCreateLoading(false); - } - setShowCreate(val); }} > - - + - setCreateForm({ @@ -510,28 +555,23 @@ export default function Space() { { - if (!val) { - setJoinForm({ - invite_code: "", - }); - setJoinLoading(false); - } - setShowJoin(val); }} > - + - { setJoinForm({ @@ -550,7 +590,7 @@ export default function Space() { - + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file