app-account-monorepo 技术栈分析报告

5 阅读10分钟

一、项目概览

app-account-monorepo 是 账号维护主线业务的 monorepo 项目,采用 pnpm workspace + Turborepo 组织架构。

属性内容
项目名称app-account-monorepo
描述账号维护主线业务的应用项目仓库
包管理器pnpm (v7/v8)
工程化方案pnpm workspace + Turborepo
语言TypeScript (strict 模式)
内部注册源http://admin.npm.oppoer.me

二、整体技术栈一览

分类技术栈说明
包管理pnpm workspacemonorepo 包管理
任务编排Turborepo并行构建/任务执行
编程语言TypeScript + React全线 TypeScript 严格模式
UI 框架React 18(主)/ React 17(个别遗留应用)组件化开发
构建工具Vite 4.x(主)+ @uc/scripts + Next.js 13多数应用使用 Vite
CSS 方案Sass/SCSS + CSS Modules模块化样式隔离
状态管理Mobx 6+(主)+ mobx-persist-store响应式状态管理
路由react-router-dom v6 / Next.js RouterSPA 路由
UI 组件库@uc/color + @uca/colorui(OPPO 内部库)企业内部设计系统
HTTP 请求axios + fetchAPI 通信
动画@react-spring/web + motion交互动画
测试jest + @testing-library/react + @testing-library/jest-dom单元测试
代码检查ESLint + Prettier代码规范
安全验证@uca-internal/safe-captcha内部安全验证码方案
国际化自有 i18n 方案多语言支持
其他sentry + @uca-internal/cloud-observation错误监控与埋点

三、项目架构详解

3.1 Workspace 布局

app-account-monorepo/
├── apps/                    # 12 个应用
│   ├── account-web/         # 账号 Web 登录注册流程应用 (Vite)
│   ├── account-auth/        # 账号 APP 端零散的 H5 页面 (@uc/scripts)
│   ├── account-external-sdk/# 帐号外发 SDK Hybrid APP (@uc/scripts, React 17)
│   ├── account-auth-app-sdk/# 账号 App 授权 SDK 页面 (Vite)
│   ├── account-auth-web-sdk/# 账号 Web 授权 SDK 项目 (Vite)
│   ├── identify-engine-hybrid/  # 账号 Hybrid 核身引擎 (Vite)
│   ├── identify-engine-web/     # 账号 Web 核身引擎 (Vite)
│   ├── complete-information-system/ # 补全信息应用 (Vite)
│   ├── security-center/     # 账号 App 安全中心 (Vite)
│   ├── user-center/         # 账号 Web 个人中心 (Vite)
│   ├── user-info/           # 账号 App 个人中心 (Vite)
│   └── information/         # 账号业务信息展示 (Next.js 13)
├── packages/                # 22 个内部共享包
│   ├── @completion-internal/mobx-domain/  # 补全流程 Mobx 领域模型
│   ├── @ideg-internal/mobx-domain/        # 核身引擎 Mobx 领域模型
│   ├── @ideg-internal/react-composables/  # 核身引擎 React 组合能力
│   ├── @uca-internal/common-components-react/  # 通用 React 组件
│   ├── @uca-internal/* (多个 Vite 插件)     # 内部 Vite 插件
│   └── ...其他工具包
└── pnpm-workspace.yaml     # workspace 声明

3.2 各应用构建方式

应用构建工具React 版本说明
account-webVite 4.xReact 18核心登录注册应用
account-auth@uc/scriptsReact 18APP 端 H5 页面,旧构建系统
account-external-sdk@uc/scriptsReact 17唯一仍使用 React 17 的应用
account-auth-app-sdkVite 4.xReact 18APP 授权 SDK
account-auth-web-sdkVite 4.xReact 18Web 授权 SDK
identify-engine-hybridVite 4.xReact 18Hybrid 核身引擎
identify-engine-webVite 4.xReact 18Web 核身引擎
complete-information-systemVite 4.xReact 18信息补全
security-centerVite 4.xReact 18安全中心
user-centerVite 4.xReact 18Web 个人中心
user-infoVite 4.xReact 18APP 个人中心
informationNext.js 13React 18唯一使用 Next.js 的应用

3.3 内部包(packages)分类

类别包名说明
状态管理@completion-internal/mobx-domain补全流程 Mobx 领域模型
@ideg-internal/mobx-domain核身引擎 Mobx 领域模型
React 工具@ideg-internal/react-composables核身引擎 React Hook 组合能力
组件库@uca-internal/common-components-react通用 React 组件
@uca-internal/safe-captcha-react安全验证码 React 组件
SDK@uca-internal/safe-captcha-sdk安全验证码 SDK
@uca-internal/complete-info-sdk补全信息 SDK
工具库@uca-internal/is-type类型判断
@uca-internal/mini-scheduler迷你调度器
@uca-internal/singleton-container单例容器
@uca-internal/app-links应用链接
@uca-internal/anchor-behavior-delegator锚点行为委托
监控@uca-internal/cloud-observation云观测
共享配置@uca-internal/shared-business-config共享业务配置
Token 管理@uca-internal/processtoken-managerProcessToken 管理
@uca-internal/update-ptmanager-interceptorPT Manager 拦截器
CSS@uca-internal/normalize-cssCSS 归一化
Vite 插件@uca-internal/vite-plugin-* (7个)内部 Vite 构建插件

四、构建工具详解

4.1 Vite 4.x 配置

Vite 是项目主要的构建工具,配置了以下关键插件:

  • @vitejs/plugin-react — 提供 React Fast Refresh
  • @vitejs/plugin-legacy — 兼容 ES5 旧浏览器
  • @uca-internal/vite-plugin-auto-entry — 自动入口文件
  • @uca-internal/vite-plugin-auto-mock-import — 自动 Mock 导入
  • @uca-internal/vite-plugin-history-fallback — History Fallback
  • @uca-internal/vite-plugin-html-folder — HTML 文件夹支持
  • @uca-internal/vite-plugin-inject-idcconfig — 注入 IDC 配置
  • @uca-internal/vite-plugin-resource-restore — 资源恢复
  • rollup-plugin-visualizer — 构建分析可视化

4.2 CSS 预处理

  • Sass/SCSS — 主要 CSS 预处理器
  • CSS Modules — 使用 .module.scss 文件实现样式隔离
  • classnames — 条件样式拼接

五、Mobx 状态管理(重点)

5.1 使用概况

Mobx 是本项目唯一的正式状态管理方案,未被使用的方案包括 Redux、Zustand、Valtio、Jotai 等。

统计项数值
导入 mobx 文件数~56 个
导入 mobx-react 文件数~149+ 个
使用 mobx-react-lite0
使用 mobx-state-tree0
Observable 定义~42 个文件
Action 定义~137 个文件
Computed 定义~20 个文件
Reaction 使用~11 个文件
Autorun 使用~11 个文件

5.2 使用的 Mobx 包

包名用途
mobx核心库:observable、action、computed、reaction、autorun
mobx-reactReact 集成:observer HOC、Observer 组件
mobx-persist-store状态持久化:makePersistable、clearPersistedStore、isHydrated

5.3 Mobx 全局配置

所有应用统一采用 Mobx 严格模式配置:

const mobxStrictModeConfig = {
  useProxies: "ifavailable",         // 优先使用 Proxy 实现
  enforceActions: "always",          // 强制所有状态修改必须通过 action
  computedRequiresReaction: true,    // computed 必须在 reactive 上下文中使用
  observableRequiresReaction: true,  // observable 读取必须在 reactive 上下文中
  reactionRequiresObservable: true,  // reaction 必须依赖 observable
}

该配置确保了:

  • 状态修改必须通过 action,不能在 action 外修改 observable
  • 计算属性必须被响应式使用,避免不必要的计算
  • 所有响应式依赖关系清晰可追踪

5.4 核心 API 用法

5.4.1 makeObservable — 声明响应式成员

使用 Mobx 6+ 推荐的 makeObservable API,在构造函数中显式声明成员类型:

import { makeObservable, observable, action, computed } from "mobx"

class ExampleStore {
  // 响应式状态
  count: number = 0
  name: string = ""

  // 计算属性
  get greeting(): string {
    return `Hello, ${this.name}!`
  }

  constructor() {
    makeObservable(this, {
      count: observable,        // 可观察状态
      name: observable,         // 可观察状态
      greeting: computed,       // 计算属性
      increment: action,        // 修改状态的动作
      setName: action,          // 修改状态的动作
    })
  }

  increment() {
    this.count++
  }

  setName(name: string) {
    this.name = name
  }
}

注:极少数简单 Store 使用 makeAutoObservable(如 OneplusAuthorizeEntryStore),它会自动推断所有属性的响应式类型。

5.4.2 computed — 派生计算属性

基于 observable 自动缓存的派生值,仅在依赖变化时重新计算:

// RegionSwitchStore 中的 computed 示例
get checkBirthdayRequired(): boolean {
  if (!this.baseInfo.birthday) return true
  const { countryCode } = this.currCoutryItem
  const minAge = this.minorRestrictionMap[countryCode] || this.minorRestrictionMap.default
  const requiredAge = Number(minAge)
  const age = dayjs(this.baseInfo.birthday).diff(dayjs(), "year")
  return age < requiredAge
}

5.4.3 action — 状态修改操作

所有状态修改都封装在 action 中,符合 Mobx 严格模式要求:

// action 示例
setCurrCountryItemAction(country: Country): void {
  this.currCoutryItem = country
  this.birthday = ""
  this.baseInfo = {}
  this.expectedMigrationTime = 0
  this.processToken = null
}

异步 action 处理模式:

async fetchDataAction(): Promise<void> {
  try {
    const result = await api.fetchSomeData()
    runInAction(() => {
      this.data = result  // 异步回调中通过 runInAction 修改状态
    })
  } catch (error) {
    runInAction(() => {
      this.error = error
    })
  }
}

5.4.4 reaction / autorun — 响应式副作用

用于在特定状态变化时执行副作用逻辑:

// reaction 示例:监听特定 observable 变化时执行
reaction(
  () => this.currCoutryItem,
  (newCountry) => {
    // 国家切换时自动更新依赖数据
    this.fetchCountryInfo(newCountry.countryCode)
  }
)

// autorun 示例:自动追踪所有依赖,变化时重新执行
autorun(() => {
  if (this.userInfo && this.isLoggedIn) {
    console.log(`用户 ${this.userInfo.name} 已登录`)
  }
})

5.5 Mobx 持久化方案 — mobx-persist-store

项目使用 mobx-persist-store 实现 Store 状态的本地持久化,典型配置:

import { makePersistable, clearPersistedStore, isHydrated } from "mobx-persist-store"
import { makeObservable, observable, action, computed } from "mobx"

class RegionSwitchStore {
  // ... 字段定义

  constructor() {
    makeObservable(this, { /* ... */ })
    this.initializePersistence()
  }

  private initializePersistence(): void {
    makePersistable(this, {
      name: "RegionSwitchStore_UC",     // 存储 key
      properties: [                      // 持久化白名单
        "currCountryItem",
        "preCheckTicket",
        "verificationId",
        "bindPhoneForm",
        "conflictAccountInfo",
      ],
      storage: inOnlineEnv
        ? encryptedStorage              // 线上使用加密存储
        : window.localStorage,          // 开发环境使用 localStorage
      expireIn: 60 * 60 * 1000,         // 60 分钟过期
      removeOnExpiration: true,         // 过期自动清除
    })
  }

  // 检查持久化是否已恢复
  get isHydrated(): boolean {
    return isPersistStoreHydrated(this)
  }

  // 手动清理持久化数据
  clearPersistedData(): Promise<void> {
    this.currCoutryItem = DEFAULT_COUNTRY
    this.birthday = ""
    // ... 重置所有字段
    return clearPersistedStore(this)
  }
}

持久化设计要点

  • 采用白名单机制,只持久化需要跨页面/跨会话保持的状态(如用户选中的国家、表单进度)
  • 区分"持久化字段"(用户进度相关)和"非持久化字段"(页面流转状态)
  • 支持过期时间(通常为 60 分钟)
  • 线上环境使用 encryptedStorage(加密存储),开发环境使用 localStorage
  • 通过 isHydrated computed 属性判断持久化数据是否已恢复,用于 UI 条件渲染

5.6 Store 架构模式

项目在 3 个主要应用和 2 个共享包中共有约 20+ 个 Mobx Store,主要采用以下架构模式:

模式一:DI + 执行器组合模式(主力模式)

代表 StoreAuthProcessDomainStore(account-web)、IdentifyEngineProcessDomainStore(ideg-mobx-domain)

Store
 ├── 响应式状态(observable)
 ├── 计算属性(computed)
 ├── 状态修改(action)
 ├── 领域 API(依赖注入)
 └── 执行器/引擎(子模块,持有 host 引用)
      ├── Executor 1(如 Email 执行器)
      ├── Executor 2(如 Mobile 执行器)
      └── Executor 3(如 Password 执行器)

执行器(Executor)本身也使用 Mobx,内部包含自己的 observable 状态和 computed 属性,通过 host 引用与父 Store 通信。

关键实现

class CompletionDomainStore {
  // 子执行器
  readonly completionExecutor: HybridSupportExecutor

  constructor() {
    this.completionExecutor = this.generateCompletionExecutor()
    makeObservable(this, { /* ... */ })
  }

  // 注入依赖(API 仓库)
  inject(deps: StoreDepends): void {
    this.deps = deps
  }
}

// 执行器内部也使用 Mobx
class RegisterExecutor {
  @observable currCoutryItem: Country
  @computed get ifDisplayedBirthday(): boolean { /* ... */ }
}

模式二:单例共享 Store 模式

代表 StoreUserStore(user-center)

通过惰性单例确保全局唯一实例,供多个业务 Store 共享数据:

// singleton.ts
let globalUserStore: UserStore | null = null

export function initializeUserStore(): UserStore {
  if (!globalUserStore) {
    globalUserStore = new UserStore()
  }
  return globalUserStore
}

UserCenterDomainStore 通过构造函数注入 UserStore,并通过 computed 代理访问共享数据:

class UserCenterDomainStore {
  private userStore: UserStore

  constructor(userStore: UserStore) {
    this.userStore = userStore
    this.modules = this.generateModuleExecutors()  // 组合子模块
    makeObservable(this, { /* ... */ })
  }

  // 从 UserStore 代理数据
  get userInfo() {
    return this.userStore.userInfo
  }
}

模式三:模块化领域 Store 模式

代表 StoreUserCenterDomainStore(user-center)

UserCenterDomainStore
 ├── Modules(业务模块执行器)
 │    ├── PERSONAL_INFO → PersonalInfoModule
 │    ├── ACCOUNT_SECURITY → AccountSecurityModule
 │    ├── DATA_MANAGEMENT → DataManagementModule
 │    ├── DEVICE_MANAGEMENT → DeviceManagementModule
 │    └── ABOUT → AboutModule
 └── 依赖 SingleUserStore(单例)

模式四:独立功能 Store 模式

代表 StoreRegionSwitchStoreEmailBindingStoreMobileBindingStore

每个功能模块有自己的独立 Store,包含完整的 observable、action、computed、持久化配置。Store 之间通过 UserStore 单例间接通信。

user-center Stores 一览:

UserStore(单例,共享用户数据)
├── UserCenterDomainStore(页面主 Store,组合模块)
├── RegionSwitchStore(地区切换,独立持久化 60min)
├── EmailBindingStore(邮箱绑定,独立持久化 60min)
├── MobileBindingStore(手机绑定,独立持久化 60min)
├── EmergencyContactStore(紧急联系人,独立持久化 60min)
├── RealNameAuthStore(实名认证,独立持久化 60min)
├── ScancodeLoginStore(扫码登录)
├── FreezeOrThawStore(冻结/解冻)
├── AccountDeletionStore(账号注销)
├── ChildAccountStore(儿童账号)
└── RetrievePasswordStore(找回密码)

5.7 Mobx 与 React 的连接

项目通过 observer HOCReact Context 将 Mobx Store 注入到 React 组件中。

Provider 模式(示例来自 user-center):

import { createContext, useContext } from "react"
import { observer } from "mobx-react"
import { RegionSwitchStore } from "./stores/region-switch"

const RegionSwitchContext = createContext<RegionSwitchStore | null>(null)

// 自定义 Hook:消费 Context 中的 Store
export function useRegionSwitchStore(): RegionSwitchStore {
  const store = useContext(RegionSwitchContext)
  if (!store) throw new Error("RegionSwitchContext not provided")
  return store
}

// Provider 组件
export function RegionSwitchProvider({ children, store }) {
  return (
    <RegionSwitchContext.Provider value={store}>
      {children}
    </RegionSwitchContext.Provider>
  )
}

// 在组件中使用 Mobx Store,通过 observer HOC 实现响应式
const RegionSwitchPage = observer(() => {
  const store = useRegionSwitchStore()

  return (
    <div>
      <span>当前地区: {store.currCoutryItem.countryName}</span>
      <button onClick={() => store.setCurrCountryItemAction(newCountry)}>
        切换地区
      </button>
    </div>
  )
})

直接在组件内使用 Store(适用于非嵌套 Provider 的场景):

import { observer } from "mobx-react"
import { userStore } from "./stores/user"

const UserProfile = observer(() => {
  // 直接读取 Store 数据,observer 自动追踪依赖
  return <div>{userStore.userInfo?.nickname}</div>
})

响应式渲染流程:

State Change (action)
    ↓
Mobx notifies observer
    ↓
React re-renders affected components (选择性重渲染)
    ↓
Components read latest observable values
    ↓
UI updated

5.8 Mobx 设计最佳实践(来自本项目)

  1. 所有状态修改必须经过 action — 通过 enforceActions: "always" 保证
  2. 持久化与运行时状态分离 — 在 Store 中明确区分需要持久化的字段和运行时临时字段
  3. 计算属性替代手动派生 — 使用 computed 而非在渲染函数中手动计算
  4. 执行器模式解耦 — 复杂业务逻辑封装到 Executor 中,Store 仅关注状态管理
  5. 单例共享 vs 独立 Store — 全局数据用单例,功能模块用独立 Store,按需选择
  6. 严格模式保障可维护性 — 通过 Mobx configure 开启所有严格选项,及早发现问题

六、其他关键依赖

6.1 路由方案

// react-router-dom v6
"react-router-dom": "^6.x"

主流应用使用 react-router-dom v6 提供的声明式路由,information 应用使用 Next.js 13 内置文件路由。

6.2 网络请求

  • axios — 主要 HTTP 客户端
  • fetch — 部分场景使用原生 fetch

6.3 UI 组件体系

  • @uc/color — OPPO 内部设计系统组件库(基础 UI)
  • @uca/colorui — 基于 @uc/color 的上层业务 UI 封装

6.4 动画方案

  • @react-spring/web — 在 account-web、account-external-sdk 中使用
  • motion — 在 account-web、user-center 中使用

6.5 测试体系

{
  "jest": "^29.x",
  "@testing-library/react": "^14.x",
  "@testing-library/jest-dom": "^6.x"
}

6.6 代码规范

  • ESLint — 代码静态检查(配置在 apps/*/.eslintrc.cjs
  • Prettier — 代码格式化

6.7 安全验证

  • @uca-internal/safe-captcha 系列包 — 提供安全验证码能力

七、技术栈特点总结

  1. 统一的 React 18 技术体系 — 除一个遗留应用外,全线使用 React 18 + TypeScript strict 模式
  2. Mobx 作为唯一状态管理方案 — 采用 Class-based Store + makeObservable 模式,配合 mobx-persist-store 实现会话级持久化
  3. 领域驱动设计(DDD) — Store 按照业务领域划分,通过 DI + 执行器模式组织复杂业务逻辑
  4. 内部生态丰富 — 大量使用 OPPO 内部 npm 包(UI 组件库、Vite 插件、工具库)
  5. 构建方案分化 — 主流使用 Vite 4.x,少量旧应用使用 @uc/scripts,一个应用使用 Next.js 13
  6. 严格的 Mobx 规范 — 开启所有严格模式配置确保代码可维护性
  7. 模块化的 Store 架构 — 支持单例模式、组合模式、独立功能模式等多种 Store 组织方式,适应不同场景
  8. 持久化策略成熟 — 白名单机制、过期时间、加密存储等,兼顾体验与安全