diff --git a/code/allapp/allapp-go-v3/configs/config.yaml b/code/allapp/allapp-go-v3/configs/config.yaml
index 9be48ad0..a1f67426 100644
--- a/code/allapp/allapp-go-v3/configs/config.yaml
+++ b/code/allapp/allapp-go-v3/configs/config.yaml
@@ -1,3 +1,7 @@
app:
name: allapp
port: 8080
+ base_url: /app
+wechat:
+ app_id: wxdab3e21a1f7e392f
+ app_secret: 0895443c6fedfc21cb92c9cd60de2a26
\ 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 430219b0..a080261e 100644
--- a/code/allapp/allapp-go-v3/go.mod
+++ b/code/allapp/allapp-go-v3/go.mod
@@ -7,6 +7,7 @@ require (
github.com/gofiber/fiber/v3 v3.1.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/jackc/pgx/v5 v5.9.1
+ github.com/pkg/errors v0.9.1
github.com/spf13/viper v1.21.0
go.uber.org/zap v1.27.1
gopkg.in/natefinch/lumberjack.v2 v2.2.1
@@ -15,6 +16,7 @@ require (
require (
github.com/andybalholm/brotli v1.2.0 // 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
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
@@ -31,7 +33,6 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
- github.com/pkg/errors v0.9.1 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
@@ -41,6 +42,7 @@ require (
github.com/tinylib/msgp v1.6.3 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.69.0 // indirect
+ github.com/x448/float16 v0.8.4 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.49.0 // indirect
diff --git a/code/allapp/allapp-go-v3/internal/bootstrap/server.go b/code/allapp/allapp-go-v3/internal/bootstrap/server.go
index a6bc7061..60011f72 100644
--- a/code/allapp/allapp-go-v3/internal/bootstrap/server.go
+++ b/code/allapp/allapp-go-v3/internal/bootstrap/server.go
@@ -24,7 +24,7 @@ func RunServer(cfg *config.Config) error {
app.Use(middleware.Logger())
// ===== router =====
- router.SetupRouter(app)
+ router.SetupRouter(app, cfg)
addr := fmt.Sprintf(":%d", cfg.App.Port)
diff --git a/code/allapp/allapp-go-v3/internal/config/config.go b/code/allapp/allapp-go-v3/internal/config/config.go
index 9f1adb99..b1125e1e 100644
--- a/code/allapp/allapp-go-v3/internal/config/config.go
+++ b/code/allapp/allapp-go-v3/internal/config/config.go
@@ -7,11 +7,13 @@ type Config struct {
JWT JwtConfig `mapstructure:"jwt"`
UniqueID UniqueIDConfig `mapstructure:"unique_id"`
Postgres PostgresConfig `mapstructure:"postgres"`
+ Wechat WechatConfig `mapstructure:"wechat"`
}
type AppConfig struct {
- Name string `mapstructure:"name"`
- Port int `mapstructure:"port"`
+ Name string `mapstructure:"name"`
+ Port int `mapstructure:"port"`
+ BaseUrl string `mapstructure:"base_url"`
}
type PostgresConfig struct {
@@ -36,3 +38,8 @@ type UniqueIDConfig struct {
DataCenterID uint16 `mapstructure:"datacenter_id"`
WorkerID uint16 `mapstructure:"worker_id"`
}
+
+type WechatConfig struct {
+ AppId string `mapstructure:"app_id"`
+ AppSecret string `mapstructure:"app_secret"`
+}
diff --git a/code/allapp/allapp-go-v3/internal/config/loader.go b/code/allapp/allapp-go-v3/internal/config/loader.go
index c1bb3d04..d29d79bd 100644
--- a/code/allapp/allapp-go-v3/internal/config/loader.go
+++ b/code/allapp/allapp-go-v3/internal/config/loader.go
@@ -52,6 +52,7 @@ func Load() (*Config, error) {
func setDefaults(v *viper.Viper) {
v.SetDefault("app.name", "allapp-go")
v.SetDefault("app.port", 8080)
+ v.SetDefault("app.base_url", "app")
// 可以继续扩展
v.SetDefault("unique_id.datacenter_id", 1)
diff --git a/code/allapp/allapp-go-v3/internal/handle/auth.go b/code/allapp/allapp-go-v3/internal/handle/auth.go
new file mode 100644
index 00000000..a89b78ca
--- /dev/null
+++ b/code/allapp/allapp-go-v3/internal/handle/auth.go
@@ -0,0 +1,201 @@
+package handle
+
+import (
+ "allapp-go/internal/errors"
+ "allapp-go/internal/httpx"
+ "allapp-go/internal/types"
+ "allapp-go/pkg/db"
+ "allapp-go/pkg/jwtx"
+ "allapp-go/pkg/uniqueid"
+ "allapp-go/pkg/wechat"
+ "fmt"
+
+ "github.com/gofiber/fiber/v3"
+ "github.com/gofiber/fiber/v3/log"
+)
+
+// LoginQq ======================== QQ 登录 ========================
+func LoginQq(c fiber.Ctx) error {
+ var req types.LoginQqDTO
+ vo := new(types.LoginVO)
+
+ if err := httpx.BindAndValidate(c, &req); err != nil {
+ return errors.WithStack(err)
+ }
+
+ return handleThirdLogin(c, req.Openid, req.Nickname, req.Avatar, 1, vo)
+}
+
+// LoginWechat ======================== 微信登录 ========================
+func LoginWechat(c fiber.Ctx) error {
+ var req types.LoginWechatDTO
+ vo := new(types.LoginVO)
+
+ if err := httpx.BindAndValidate(c, &req); err != nil {
+ return errors.WithStack(err)
+ }
+
+ openid, token, err := wechat.GetWechatAccess(req.Code)
+ if err != nil {
+ log.Errorw("wechat access failed", "code", req.Code, "error", err)
+ return httpx.Fail(c, "微信登录失败,请重试")
+ }
+
+ nickname, avatar, err := wechat.GetWechatUserInfo(token, openid)
+ if err != nil {
+ log.Errorw("wechat userinfo failed", "openid", openid, "error", err)
+ return httpx.Fail(c, "微信登录失败,请重试")
+ }
+
+ return handleThirdLogin(c, openid, nickname, avatar, 0, vo)
+}
+
+// ======================== 第三方登录主流程 ========================
+func handleThirdLogin(
+ c fiber.Ctx,
+ openid string,
+ nickname string,
+ avatar string,
+ loginType int16,
+ vo *types.LoginVO,
+) error {
+
+ dbClient := db.New()
+
+ user, err := getUserByOpenID(dbClient, c, openid, loginType)
+ if err != nil {
+ return errors.WithStack(err)
+ }
+
+ // 不存在 -> 注册
+ if user == nil {
+ return registerUser(dbClient, c, openid, nickname, avatar, loginType, vo)
+ }
+
+ // ========= 校验状态 =========
+ id := toInt64(user["id"])
+ status := toInt64(user["status"])
+
+ if status == 0 {
+ return httpx.Fail(c, "账号已被禁用")
+ }
+
+ // ========= 更新登录时间 =========
+ if err := dbClient.Update(
+ c.Context(),
+ "user",
+ "id",
+ map[string]any{
+ "id": id,
+ "last_login_time": "NOW()",
+ },
+ ); err != nil {
+ return errors.WithStack(err)
+ }
+
+ // ========= token =========
+ token, err := jwtx.CreateToken(c.Context(), map[string]any{
+ "id": id,
+ })
+ if err != nil {
+ return errors.WithStack(err)
+ }
+
+ // ========= 返回 =========
+ vo.Token = token
+ vo.UserId = id
+ vo.Nickname, _ = user["nickname"].(string)
+ vo.Avatar, _ = user["avatar"].(string)
+
+ return httpx.OK(c, vo)
+}
+
+// ======================== 注册 ========================
+func registerUser(
+ dbClient *db.Client,
+ c fiber.Ctx,
+ openid, nickname, avatar string,
+ loginType int16,
+ vo *types.LoginVO,
+) error {
+ userId := uniqueid.NextId()
+
+ if nickname == "" {
+ nickname = fmt.Sprintf("用户_%d", userId)
+ }
+ if avatar == "" {
+ avatar = "https://default-avatar-url.com/default.png"
+ }
+
+ err := dbClient.WithTx(c.Context(), func(tx *db.Client) error {
+
+ if err := tx.Insert(c.Context(), "user", "id", map[string]any{
+ "id": userId,
+ "nickname": nickname,
+ "avatar": avatar,
+ "status": 1,
+ "create_time": "NOW()",
+ "update_time": "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()",
+ }); err != nil {
+ return err
+ }
+
+ return nil
+ })
+
+ if err != nil {
+ return errors.WithStack(err)
+ }
+
+ token, err := jwtx.CreateToken(c.Context(), map[string]any{
+ "id": userId,
+ })
+ if err != nil {
+ return errors.WithStack(err)
+ }
+
+ vo.Token = token
+ vo.UserId = userId
+ vo.Nickname = nickname
+ vo.Avatar = avatar
+
+ return httpx.OK(c, vo)
+}
+
+// ======================== DB 查询封装(去重复 SQL) ========================
+func getUserByOpenID(dbClient *db.Client, c fiber.Ctx, openid string, loginType int16) (map[string]any, error) {
+
+ 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 = ?",
+ []any{openid, loginType},
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ if len(users) == 0 {
+ return nil, nil
+ }
+
+ return users[0], nil
+}
+
+// ======================== 类型工具(极简版) ========================
+func toInt64(v any) int64 {
+ i, _ := v.(int64)
+ return i
+}
diff --git a/code/allapp/allapp-go-v3/internal/handle/data_save.go b/code/allapp/allapp-go-v3/internal/handle/data_save.go
index f550b99c..9bcae627 100644
--- a/code/allapp/allapp-go-v3/internal/handle/data_save.go
+++ b/code/allapp/allapp-go-v3/internal/handle/data_save.go
@@ -1,6 +1,7 @@
package handle
import (
+ "allapp-go/internal/errors"
"allapp-go/internal/httpx"
"allapp-go/internal/types"
"allapp-go/pkg/db"
@@ -12,7 +13,7 @@ func SaveData(c fiber.Ctx) error {
var req []types.SaveDataReq
if err := httpx.BindAndValidate(c, &req); err != nil {
- return err
+ return errors.WithStack(err)
}
if len(req) == 0 {
@@ -49,7 +50,7 @@ func SaveData(c fiber.Ctx) error {
})
if err != nil {
- return err
+ return errors.WithStack(err)
}
return httpx.OK(c, nil)
diff --git a/code/allapp/allapp-go-v3/internal/router/router.go b/code/allapp/allapp-go-v3/internal/router/router.go
index 57f3fae7..11fb63cf 100644
--- a/code/allapp/allapp-go-v3/internal/router/router.go
+++ b/code/allapp/allapp-go-v3/internal/router/router.go
@@ -1,14 +1,20 @@
package router
import (
+ "allapp-go/internal/config"
"allapp-go/internal/handle"
"github.com/gofiber/fiber/v3"
)
-func SetupRouter(app *fiber.App) {
+func SetupRouter(app *fiber.App, cfg *config.Config) {
+ api := app.Group(cfg.App.BaseUrl)
+
// ==================== data ====================
- app.Post("/data/loadData", handle.LoadData)
- app.Post("/data/loadDataBySql", handle.LoadDataBySql)
- app.Post("/data/saveData", handle.SaveData)
+ api.Post("/data/loadData", handle.LoadData)
+ api.Post("/data/loadDataBySql", handle.LoadDataBySql)
+ api.Post("/data/saveData", handle.SaveData)
+
+ api.Post("/auth/login/qq", handle.LoginQq)
+ api.Post("/auth/login/wechat", handle.LoginWechat)
}
diff --git a/code/allapp/allapp-go-v3/internal/types/auth.go b/code/allapp/allapp-go-v3/internal/types/auth.go
new file mode 100644
index 00000000..34d23586
--- /dev/null
+++ b/code/allapp/allapp-go-v3/internal/types/auth.go
@@ -0,0 +1,18 @@
+package types
+
+type LoginQqDTO struct {
+ Openid string `json:"openid" validate:"required"`
+ Nickname string `json:"nickname"`
+ Avatar string `json:"avatar"`
+}
+
+type LoginWechatDTO struct {
+ Code string `json:"code" validate:"required"`
+}
+
+type LoginVO struct {
+ Token string `json:"token"`
+ UserId int64 `json:"userId"`
+ Nickname string `json:"nickname"`
+ Avatar string `json:"avatar"`
+}
diff --git a/code/allapp/allapp-go-v3/pkg/common/common.go b/code/allapp/allapp-go-v3/pkg/common/common.go
new file mode 100644
index 00000000..c95b8470
--- /dev/null
+++ b/code/allapp/allapp-go-v3/pkg/common/common.go
@@ -0,0 +1,45 @@
+package common
+
+import (
+ "crypto/rand"
+ "encoding/json"
+ "math/big"
+)
+
+// JSONStringToStruct 将 JSON 字符串反序列化到指定结构体类型
+func JSONStringToStruct[T any](jsonStr string) (*T, error) {
+ var result T
+ if err := json.Unmarshal([]byte(jsonStr), &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+func JSONStringToMap(jsonStr string) (map[string]interface{}, error) {
+ var result map[string]interface{}
+ if err := json.Unmarshal([]byte(jsonStr), &result); err != nil {
+ return nil, err
+ }
+ return result, nil
+}
+
+// GenerateCode 生成指定长度的随机邀请码(大写字母+数字)
+// 不会返回错误,如果随机失败,会自动重试
+func GenerateCode(length int) string {
+ const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+ code := make([]byte, length)
+
+ for i := 0; i < length; i++ {
+ for {
+ num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
+ if err != nil {
+ // 随机失败,重试
+ continue
+ }
+ code[i] = charset[num.Int64()]
+ break
+ }
+ }
+
+ return string(code)
+}
diff --git a/code/allapp/allapp-go-v3/pkg/db/select.go b/code/allapp/allapp-go-v3/pkg/db/select.go
index 41f29cb1..6ea8caaa 100644
--- a/code/allapp/allapp-go-v3/pkg/db/select.go
+++ b/code/allapp/allapp-go-v3/pkg/db/select.go
@@ -13,7 +13,7 @@ func (c *Client) LoadData(
orderBy string,
searchColumns []string,
args []any,
-) ([][]any, error) {
+) ([]map[string]any, error) {
var where string
@@ -50,12 +50,14 @@ func (c *Client) LoadDataBySQL(
ctx context.Context,
sql string,
args []any,
-) ([][]any, error) {
+) ([]map[string]any, error) {
return c.query(ctx, sql, args...)
}
-// 统一查询方法(核心优化)
-func (c *Client) query(ctx context.Context, sql string, args ...any) ([][]any, error) {
+// ==========================
+// 核心查询方法(已升级)
+// ==========================
+func (c *Client) query(ctx context.Context, sql string, args ...any) ([]map[string]any, error) {
sql = convertPlaceholder(sql)
rows, err := c.exec().Query(ctx, sql, args...)
@@ -64,7 +66,10 @@ func (c *Client) query(ctx context.Context, sql string, args ...any) ([][]any, e
}
defer rows.Close()
- result := make([][]any, 0, 16)
+ // 获取字段名
+ fields := rows.FieldDescriptions()
+
+ result := make([]map[string]any, 0, 16)
for rows.Next() {
values, err := rows.Values()
@@ -72,8 +77,13 @@ func (c *Client) query(ctx context.Context, sql string, args ...any) ([][]any, e
return nil, err
}
- row := make([]any, len(values))
- copy(row, values)
+ row := make(map[string]any, len(values))
+
+ for i, f := range fields {
+ if i < len(values) {
+ row[string(f.Name)] = values[i]
+ }
+ }
result = append(result, row)
}
diff --git a/code/allapp/allapp-go-v3/pkg/requestx/requestx.go b/code/allapp/allapp-go-v3/pkg/requestx/requestx.go
new file mode 100644
index 00000000..fd3f1cc2
--- /dev/null
+++ b/code/allapp/allapp-go-v3/pkg/requestx/requestx.go
@@ -0,0 +1,99 @@
+package requestx
+
+import (
+ "context"
+ "time"
+
+ "github.com/gofiber/fiber/v3/client"
+)
+
+// 全局 Fiber Client
+var cc = client.New()
+
+func init() {
+ cc.SetTimeout(10 * time.Second)
+}
+
+// mergeConfig 合并默认配置和用户配置
+func mergeConfig(defaultCfg, userCfg client.Config) client.Config {
+ if userCfg.Ctx != nil {
+ defaultCfg.Ctx = userCfg.Ctx
+ }
+ if userCfg.Body != nil {
+ defaultCfg.Body = userCfg.Body
+ }
+ if userCfg.Header != nil {
+ if defaultCfg.Header == nil {
+ defaultCfg.Header = map[string]string{}
+ }
+ for k, v := range userCfg.Header {
+ defaultCfg.Header[k] = v
+ }
+ }
+ if userCfg.Param != nil {
+ defaultCfg.Param = userCfg.Param
+ }
+ if userCfg.PathParam != nil {
+ defaultCfg.PathParam = userCfg.PathParam
+ }
+ if userCfg.FormData != nil {
+ defaultCfg.FormData = userCfg.FormData
+ }
+ if userCfg.Timeout != 0 {
+ defaultCfg.Timeout = userCfg.Timeout
+ }
+ if userCfg.UserAgent != "" {
+ defaultCfg.UserAgent = userCfg.UserAgent
+ }
+ if userCfg.Referer != "" {
+ defaultCfg.Referer = userCfg.Referer
+ }
+ defaultCfg.DisablePathNormalizing = userCfg.DisablePathNormalizing
+ defaultCfg.MaxRedirects = userCfg.MaxRedirects
+
+ return defaultCfg
+}
+
+func Get(url string, cfg client.Config) (string, int, error) {
+ defaultCfg := client.Config{
+ Ctx: context.Background(),
+ }
+ finalCfg := mergeConfig(defaultCfg, cfg)
+
+ resp, err := cc.Get(url, finalCfg)
+ if err != nil {
+ return "", 0, err
+ }
+
+ return string(resp.Body()), resp.StatusCode(), nil
+}
+
+func PostJSON(url string, data any, cfg client.Config) (string, int, error) {
+ defaultCfg := client.Config{
+ Ctx: context.Background(),
+ Body: data,
+ Header: map[string]string{"Content-Type": "application/json"},
+ }
+ finalCfg := mergeConfig(defaultCfg, cfg)
+
+ resp, err := cc.Post(url, finalCfg)
+ if err != nil {
+ return "", 0, err
+ }
+ return string(resp.Body()), resp.StatusCode(), nil
+}
+
+func PostForm(url string, formData map[string]string, cfg client.Config) (string, int, error) {
+ defaultCfg := client.Config{
+ Ctx: context.Background(),
+ FormData: formData,
+ Header: map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
+ }
+ finalCfg := mergeConfig(defaultCfg, cfg)
+
+ resp, err := cc.Post(url, finalCfg)
+ if err != nil {
+ return "", 0, err
+ }
+ return string(resp.Body()), resp.StatusCode(), nil
+}
diff --git a/code/allapp/allapp-go-v3/pkg/wechat/wechat.go b/code/allapp/allapp-go-v3/pkg/wechat/wechat.go
new file mode 100644
index 00000000..6555c4c5
--- /dev/null
+++ b/code/allapp/allapp-go-v3/pkg/wechat/wechat.go
@@ -0,0 +1,102 @@
+package wechat
+
+import (
+ "allapp-go/pkg/common"
+ "allapp-go/pkg/requestx"
+ "fmt"
+ "sync"
+
+ "github.com/gofiber/fiber/v3/client"
+)
+
+type jwtManager struct {
+ appid string
+ secret string
+}
+
+var (
+ manager *jwtManager
+ once sync.Once
+)
+
+func Init(appid string, secret string) {
+ once.Do(func() {
+ manager = &jwtManager{
+ appid: appid,
+ secret: secret,
+ }
+ })
+}
+
+func getManager() *jwtManager {
+ if manager == nil {
+ panic("wechat未初始化,请先调用Init")
+ }
+ return manager
+}
+
+func GetWechatAccess(code string) (openid string, accessToken string, err error) {
+ bodyData, bodyCode, err := requestx.Get(
+ "https://api.weixin.qq.com/sns/oauth2/access_token",
+ client.Config{
+ Param: map[string]string{
+ "appid": getManager().appid,
+ "secret": getManager().secret,
+ "code": code,
+ "grant_type": "authorization_code",
+ },
+ },
+ )
+
+ if err != nil {
+ return "", "", err
+ }
+
+ if bodyCode != 200 {
+ return "", "", fmt.Errorf("微信Http请求失败: %d", bodyCode)
+ }
+
+ accessMap, err := common.JSONStringToMap(bodyData)
+ if err != nil {
+ return "", "", err
+ }
+
+ accessToken, _ = accessMap["access_token"].(string)
+ openid, _ = accessMap["openid"].(string)
+
+ if accessToken == "" || openid == "" {
+ return "", "", fmt.Errorf("获取微信登录凭证失败: %s", bodyData)
+ }
+
+ return
+}
+
+func GetWechatUserInfo(accessToken, openid string) (nickname, avatar string, err error) {
+ bodyData, bodyCode, err := requestx.Get(
+ "https://api.weixin.qq.com/sns/userinfo",
+ client.Config{
+ Param: map[string]string{
+ "access_token": accessToken,
+ "openid": openid,
+ },
+ },
+ )
+
+ if err != nil {
+ return "", "", err
+ }
+
+ if bodyCode != 200 {
+ return "", "", fmt.Errorf("微信Http请求失败")
+ }
+
+ userMap, err := common.JSONStringToMap(bodyData)
+ if err != nil {
+ return "", "", err
+ }
+
+ nickname, _ = userMap["nickname"].(string)
+ avatar, _ = userMap["headimgurl"].(string)
+
+ return
+}
diff --git a/code/app/app-rn4/app.json b/code/app/app-rn4/app.json
index b007ead9..9da75177 100644
--- a/code/app/app-rn4/app.json
+++ b/code/app/app-rn4/app.json
@@ -5,8 +5,12 @@
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
- "scheme": "apprn",
"userInterfaceStyle": "automatic",
+ "scheme": [
+ "oneao",
+ "tencent102826474",
+ "wxdab3e21a1f7e392f"
+ ],
"ios": {
"icon": "./assets/expo.icon"
},
@@ -18,7 +22,7 @@
"monochromeImage": "./assets/images/android-icon-monochrome.png"
},
"predictiveBackGestureEnabled": false,
- "package": "com.anonymous.apprn"
+ "package": "cn.oneao.app"
},
"web": {
"output": "static",
@@ -26,6 +30,7 @@
"bundler": "metro"
},
"plugins": [
+ "./plugins/withAndroidSignature",
"expo-router",
[
"expo-splash-screen",
@@ -37,7 +42,23 @@
}
}
],
- "expo-web-browser"
+ "expo-web-browser",
+ [
+ "expo-qq",
+ {
+ "appId": "102826474"
+ }
+ ],
+ [
+ "expo-build-properties",
+ {
+ "android": {
+ "usesCleartextTraffic": true,
+ "extraProguardRules": "-keep class com.tencent.mm.opensdk.** { *; }\n-keep class com.tencent.wxop.** { *; }\n-keep class com.tencent.mm.sdk.** { *; }"
+ }
+ }
+ ],
+ "expo-wechat"
],
"experiments": {
"typedRoutes": true,
diff --git a/code/app/app-rn4/credentials/credentials.json b/code/app/app-rn4/credentials/credentials.json
new file mode 100644
index 00000000..4f43db65
--- /dev/null
+++ b/code/app/app-rn4/credentials/credentials.json
@@ -0,0 +1,17 @@
+{
+ "android": {
+ "keystore": {
+ "keystorePath": "credentials/oneao-android.keystore",
+ "keystorePassword": "zhang520..",
+ "keyAlias": "oneao-android",
+ "keyPassword": "zhang520.."
+ }
+ },
+ "ios": {
+ "provisioningProfilePath": "ios/certs/profile.mobileprovision",
+ "distributionCertificate": {
+ "path": "ios/certs/dist-cert.p12",
+ "password": "password"
+ }
+ }
+}
diff --git a/code/app/app-rn4/credentials/oneao-android.keystore b/code/app/app-rn4/credentials/oneao-android.keystore
new file mode 100644
index 00000000..05b3583c
Binary files /dev/null and b/code/app/app-rn4/credentials/oneao-android.keystore differ
diff --git a/code/app/app-rn4/package.json b/code/app/app-rn4/package.json
index 07aec015..67656900 100644
--- a/code/app/app-rn4/package.json
+++ b/code/app/app-rn4/package.json
@@ -24,6 +24,7 @@
"axios": "^1.15.0",
"expo": "~55.0.15",
"expo-blur": "~55.0.14",
+ "expo-build-properties": "~55.0.13",
"expo-constants": "~55.0.14",
"expo-device": "~55.0.15",
"expo-font": "~55.0.6",
@@ -31,12 +32,14 @@
"expo-image": "~55.0.8",
"expo-linear-gradient": "~55.0.13",
"expo-linking": "~55.0.13",
+ "expo-qq": "^0.1.8",
"expo-router": "~55.0.12",
"expo-splash-screen": "~55.0.18",
"expo-status-bar": "~55.0.5",
"expo-symbols": "~55.0.7",
"expo-system-ui": "~55.0.15",
"expo-web-browser": "~55.0.14",
+ "expo-wechat": "^0.2.6",
"heroui-native": "^1.0.1",
"lucide-react-native": "^1.8.0",
"react": "19.2.0",
diff --git a/code/app/app-rn4/plugins/withAndroidSignature.js b/code/app/app-rn4/plugins/withAndroidSignature.js
new file mode 100644
index 00000000..cc48d6fe
--- /dev/null
+++ b/code/app/app-rn4/plugins/withAndroidSignature.js
@@ -0,0 +1,95 @@
+const fs = require("node:fs");
+const path = require("node:path");
+const { withAppBuildGradle } = require("expo/config-plugins");
+
+const CREDENTIAL_PATH = path.resolve(
+ __dirname,
+ "../credentials/credentials.json"
+);
+
+const KEYSTORE_PATH = path.resolve(
+ __dirname,
+ "../credentials/oneao-android.keystore"
+);
+
+module.exports = function withAndroidSignature(config) {
+ return withAppBuildGradle(config, (config) => {
+ if (config.modResults.language !== "groovy") {
+ throw new Error("only groovy build.gradle is supported");
+ }
+
+ config.modResults.contents = setAndroidSignature(
+ config.modResults.contents
+ );
+
+ return config;
+ });
+};
+
+// ======================== 主入口 ========================
+function setAndroidSignature(appBuildGradle) {
+ if (!fs.existsSync(CREDENTIAL_PATH)) {
+ console.warn("⚠️ credentials.json 不存在,跳过签名配置");
+ return appBuildGradle;
+ }
+
+ 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 {
+ release {
+ 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)}
+ }
+
+ 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,可以改这里)
+ output = output.replace(
+ /signingConfig\s+signingConfigs\.debug/g,
+ "signingConfig signingConfigs.release"
+ );
+
+ return output;
+}
\ No newline at end of file
diff --git a/code/app/app-rn4/pnpm-lock.yaml b/code/app/app-rn4/pnpm-lock.yaml
index e5ef3d18..208c7039 100644
--- a/code/app/app-rn4/pnpm-lock.yaml
+++ b/code/app/app-rn4/pnpm-lock.yaml
@@ -41,6 +41,9 @@ importers:
expo-blur:
specifier: ~55.0.14
version: 55.0.14(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)
+ expo-build-properties:
+ specifier: ~55.0.13
+ version: 55.0.13(expo@55.0.15)
expo-constants:
specifier: ~55.0.14
version: 55.0.14(expo@55.0.15)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(typescript@5.9.3)
@@ -62,6 +65,9 @@ importers:
expo-linking:
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)(typescript@5.9.3)
+ expo-qq:
+ specifier: ^0.1.8
+ version: 0.1.8(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)
expo-router:
specifier: ~55.0.12
version: 55.0.12(@expo/log-box@55.0.10)(@expo/metro-runtime@55.0.9)(@types/react@19.2.14)(expo-constants@55.0.14)(expo-font@55.0.6)(expo-linking@55.0.13)(expo@55.0.15)(react-dom@19.2.0(react@19.2.0))(react-native-gesture-handler@2.30.1(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-reanimated@4.2.1(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))(react-native-safe-area-context@5.6.2(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-screens@4.23.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-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)
@@ -80,6 +86,9 @@ importers:
expo-web-browser:
specifier: ~55.0.14
version: 55.0.14(expo@55.0.15)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))
+ expo-wechat:
+ specifier: ^0.2.6
+ version: 0.2.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)
heroui-native:
specifier: ^1.0.1
version: 1.0.2(f8f492cc9ab1e25a4f7a8dde00438ce1)
@@ -2205,6 +2214,11 @@ packages:
react: '*'
react-native: '*'
+ expo-build-properties@55.0.13:
+ resolution: {integrity: sha512-UYZhUKyh7YQhbJdkBvo68WUQ7fOtZeSV7F8kfYkjEiN/ADRHG0WfEIiddvGfi9cH/5iwpptv/+Lu5cx6uvfegA==}
+ peerDependencies:
+ expo: '*'
+
expo-constants@55.0.14:
resolution: {integrity: sha512-l23QVQCYBPKT5zbxxZdJeuhiunadvWdjcQ9+GC8h+02jCoLmWRk20064nCINnQTP3Hf+uLPteUiwYrJd0e446w==}
peerDependencies:
@@ -2280,6 +2294,13 @@ packages:
react-native-worklets:
optional: true
+ expo-qq@0.1.8:
+ resolution: {integrity: sha512-Khuu4LWOjpYGGBamtUFBxaA5q8oIcFiVgXkyRVqfNdSEP6Z0Fy3eBj3HuXP8mxonT/hJjG3GCX7ypJD7M9nU/g==}
+ peerDependencies:
+ expo: '*'
+ react: '*'
+ react-native: '*'
+
expo-router@55.0.12:
resolution: {integrity: sha512-Bm6IhI0Kl5/tDlCHPms8jDqy1O6HLHIOrMsEmmAQ5Lgg5UBtDfRThEyHPVOLNTOs8e7/bG/Ftz6a4UgQVA+NhQ==}
peerDependencies:
@@ -2354,6 +2375,13 @@ packages:
expo: '*'
react-native: '*'
+ expo-wechat@0.2.6:
+ resolution: {integrity: sha512-hLWFPDqSKwEgG8Jm0cTthyQftD/ip9xjJLQUEd+fJJVq8YEiEt0gJCCgCEMbr1SrEjoK4TUlkG/HETZobNReuQ==}
+ peerDependencies:
+ expo: '*'
+ react: '*'
+ react-native: '*'
+
expo@55.0.15:
resolution: {integrity: sha512-sHIvqG477UU1jZHhaexXbUgsU7y+xnYZqDW1HrUkEBYiuEb5lobvWLmwea76EBVkityQx46UDtepFtarpUJQqQ==}
hasBin: true
@@ -6385,6 +6413,13 @@ 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-build-properties@55.0.13(expo@55.0.15):
+ dependencies:
+ '@expo/schema-utils': 55.0.3
+ 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)
+ resolve-from: 5.0.0
+ semver: 7.7.4
+
expo-constants@55.0.14(expo@55.0.15)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(typescript@5.9.3):
dependencies:
'@expo/config': 55.0.15(typescript@5.9.3)
@@ -6467,6 +6502,12 @@ snapshots:
optionalDependencies:
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)
+ expo-qq@0.1.8(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):
+ 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)
+ react: 19.2.0
+ react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
+
expo-router@55.0.12(@expo/log-box@55.0.10)(@expo/metro-runtime@55.0.9)(@types/react@19.2.14)(expo-constants@55.0.14)(expo-font@55.0.6)(expo-linking@55.0.13)(expo@55.0.15)(react-dom@19.2.0(react@19.2.0))(react-native-gesture-handler@2.30.1(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-reanimated@4.2.1(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))(react-native-safe-area-context@5.6.2(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-screens@4.23.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-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/log-box': 55.0.10(@expo/dom-webview@55.0.5)(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)
@@ -6556,6 +6597,12 @@ 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)
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
+ expo-wechat@0.2.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):
+ 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)
+ react: 19.2.0
+ react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
+
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):
dependencies:
'@babel/runtime': 7.29.2
diff --git a/code/app/app-rn4/src/api/index.ts b/code/app/app-rn4/src/api/index.ts
deleted file mode 100644
index e6dc2675..00000000
--- a/code/app/app-rn4/src/api/index.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { Method } from "alova";
-import type { AxiosError } from "axios";
-import { axiosRequestAdapter } from "@alova/adapter-axios";
-import { createAlova } from "alova";
-import { toast } from "sonner-native";
-
-const alovaInstance = createAlova({
- baseURL: "",
- requestAdapter: axiosRequestAdapter(),
- timeout: 5000,
- cacheFor: null,
-});
diff --git a/code/app/app-rn4/src/app/_layout.tsx b/code/app/app-rn4/src/app/_layout.tsx
index c8995f8f..59720142 100644
--- a/code/app/app-rn4/src/app/_layout.tsx
+++ b/code/app/app-rn4/src/app/_layout.tsx
@@ -7,6 +7,7 @@ import * as SplashScreen from "expo-splash-screen";
import "@/global.css";
import { useEffect, useState } from "react";
import { Stack } from "expo-router";
+import { Toaster } from "sonner-native";
SplashScreen.preventAutoHideAsync();
@@ -52,6 +53,8 @@ export default function RootLayout() {
{!isDev && }
+
+
);
}
diff --git a/code/app/app-rn4/src/app/money/(tabs)/home/index.tsx b/code/app/app-rn4/src/app/money/(tabs)/home/index.tsx
index 4ee6e265..dfcbb7b5 100644
--- a/code/app/app-rn4/src/app/money/(tabs)/home/index.tsx
+++ b/code/app/app-rn4/src/app/money/(tabs)/home/index.tsx
@@ -1,11 +1,24 @@
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
import LayoutView from "@/components/layout/view";
import { Text } from "react-native";
import EmptyState from "@/components/EmptyStatus";
+import { loadDataApi } from "@/request/api";
export default function Home() {
const [dataList, setDataList] = useState([]);
+ useEffect(() => {
+ loadDataApi({
+ view_name: "v_finance_record",
+ search_condition: "",
+ search_columns: [],
+ order_by: "",
+ args: [],
+ }).then((res) => {
+ console.log(res);
+ });
+ }, []);
+
return (
diff --git a/code/app/app-rn4/src/request/api.ts b/code/app/app-rn4/src/request/api.ts
new file mode 100644
index 00000000..b0dba2cc
--- /dev/null
+++ b/code/app/app-rn4/src/request/api.ts
@@ -0,0 +1,32 @@
+import type { LoginQqParams, LoginResult, LoginWxParams } from "@/types/auth";
+import alovaInstance from "./index";
+
+export function loginQqApi(params: LoginQqParams) {
+ return alovaInstance.Post>(`/auth/login/qq`, params);
+}
+
+export function loginWxApi(params: LoginWxParams) {
+ return alovaInstance.Post>(`/auth/login/wechat`, params);
+}
+
+interface LoadDataBySqlParams {
+ sql: string;
+ args: any[];
+}
+
+export function loadDataBySqlApi(params: LoadDataBySqlParams) {
+ return alovaInstance.Post>(`/data/loadDataBySql`, params);
+}
+
+interface LoadDataParams {
+ view_name: string;
+ search_condition: string;
+ order_by: string;
+ search_columns: string[];
+ args: string[];
+}
+
+export function loadDataApi(params: LoadDataParams) {
+ return alovaInstance.Post>(`/data/loadData`, params);
+}
+
diff --git a/code/app/app-rn4/src/request/index.ts b/code/app/app-rn4/src/request/index.ts
new file mode 100644
index 00000000..a4d78f9d
--- /dev/null
+++ b/code/app/app-rn4/src/request/index.ts
@@ -0,0 +1,81 @@
+import type { Method } from "alova";
+import type { AxiosError } from "axios";
+import { axiosRequestAdapter } from "@alova/adapter-axios";
+import { createAlova } from "alova";
+import { toast } from "sonner-native";
+import { useUserStore } from "@/store/user";
+
+enum HttpCode {
+ Success = 1000,
+ Unauthorized = 1001,
+ Fail = 500,
+ BusinessError = 2000,
+ SystemError = 3000,
+}
+
+// 仅保留「无需登录」接口
+const WhiteList = ["/auth/login/**"];
+
+function isWhiteListed(url: string) {
+ return WhiteList.some((pattern) => {
+ if (pattern.endsWith("/**")) {
+ const base = pattern.slice(0, -3);
+ return url.startsWith(base);
+ }
+ return url === pattern;
+ });
+}
+
+const alovaInstance = createAlova({
+ baseURL: process.env.EXPO_PUBLIC_API_URL,
+ requestAdapter: axiosRequestAdapter(),
+ timeout: 5000,
+ cacheFor: null,
+
+ responded: {
+ onSuccess: async (response, _method: Method) => {
+ const resData = response.data as ResponseData;
+
+ const result = {
+ ...resData,
+ isSuccess: resData.code === HttpCode.Success,
+ };
+
+ if (resData.code === HttpCode.Unauthorized) {
+ toast.error("登录状态已过期,请重新登录");
+ useUserStore.getState().setAuthExpired(true);
+ return result;
+ } else if (resData.code === HttpCode.BusinessError) {
+ toast.error(resData.message || "业务异常,请稍后再试");
+ return result;
+ } else if (resData.code === HttpCode.SystemError) {
+ toast.error(resData.message || "系统异常,请稍后再试");
+ return result;
+ }
+
+ return result;
+ },
+
+ onError: (error: AxiosError) => {
+ return Promise.reject(error);
+ },
+ },
+
+ beforeRequest(method) {
+ // 白名单接口直接放行
+ if (isWhiteListed(method.url)) return;
+
+ const userStore = useUserStore.getState();
+
+ // 未登录直接拦截
+ if (!userStore.isLogged()) {
+ userStore.setAuthExpired(true);
+ return Promise.reject(new Error("未登录"));
+ }
+
+ // 注入 JWT
+ method.config.headers.Token = userStore.userInfo!.token;
+ },
+});
+
+export default alovaInstance;
diff --git a/code/app/app-rn4/src/store/user.ts b/code/app/app-rn4/src/store/user.ts
index 5fb7aeaf..264fa1da 100644
--- a/code/app/app-rn4/src/store/user.ts
+++ b/code/app/app-rn4/src/store/user.ts
@@ -4,9 +4,15 @@ import { createJSONStorage, persist } from "zustand/middleware";
interface UserState {
userInfo: UserInfo;
+
isLogged: () => boolean;
+
setUserInfo: (userInfo: UserInfo) => void;
+
clear: () => void;
+
+ authExpired: boolean;
+ setAuthExpired: (v: boolean) => void;
}
const defaultUserInfo: UserInfo = {
@@ -21,6 +27,8 @@ export const useUserStore = create()(
(set, get) => ({
userInfo: defaultUserInfo,
+ authExpired: false,
+
isLogged: () => !!get().userInfo?.token,
setUserInfo: (userInfo) =>
@@ -33,10 +41,19 @@ export const useUserStore = create()(
userInfo: defaultUserInfo,
});
},
+
+ setAuthExpired: (v: boolean) =>
+ set({
+ authExpired: v,
+ }),
}),
{
name: "user-storage",
storage: createJSONStorage(() => AsyncStorage),
+
+ partialize: (state) => ({
+ userInfo: state.userInfo,
+ }),
},
),
);
diff --git a/code/app/app-rn4/src/types/auth.ts b/code/app/app-rn4/src/types/auth.ts
new file mode 100644
index 00000000..844731a9
--- /dev/null
+++ b/code/app/app-rn4/src/types/auth.ts
@@ -0,0 +1,16 @@
+export interface LoginQqParams {
+ openid: string
+ nickname: string
+ avatar: string
+}
+
+export interface LoginWxParams {
+ code: string
+}
+
+export interface LoginResult {
+ token: string
+ userId: number
+ nickname: string
+ avatar: string
+}
diff --git a/code/app/app-rn4/src/types/global.d.ts b/code/app/app-rn4/src/types/global.d.ts
index c816085b..1508108e 100644
--- a/code/app/app-rn4/src/types/global.d.ts
+++ b/code/app/app-rn4/src/types/global.d.ts
@@ -10,6 +10,14 @@ declare global {
SystemError = 3000,
}
+ interface ResponseData {
+ track_id: string;
+ code: number;
+ message: string;
+ data: T;
+ isSuccess: boolean;
+ }
+
interface UserInfo {
id: number;
token: string;