当前位置:首页>java>Kotlin白标代码界的一鱼多吃

Kotlin白标代码界的一鱼多吃

  • 2026-02-06 13:00:00
Kotlin白标代码界的一鱼多吃

超越共享逻辑:如何用 Kotlin Multiplatform 打造白标应用

我们是如何在生产级银行超级应用中实现 70% 代码共享,同时保持 100% 原生 UI 质感的。

本文将涵盖:

  • 两难困境: 如何在代码共享与原生用户体验之间取得平衡。
  • 解决方案: 使用 KMP 共享 UI 的“意图”,同时保持渲染的原生性。
  • 代码实战: 如何利用 Config 状态对象在 Compose 和 SwiftUI 中解耦逻辑与视图。
  • 构建流水线: JSON 配置如何驱动多客户的品牌化定制。
  • 深度剖析: 一探“功能解剖学”和共享 ViewModel,看看 70% 代码共享是如何炼成的。

在跨平台开发的世界里,代码共享和用户体验往往像一对欢喜冤家,总是处于拉锯战中。如果你共享得太多(比如使用那些死板的混合框架),UI 看起来就会像个“冒牌货”,感觉总是“不对味儿”。如果你共享得太少,你就会淹没在重复维护的苦海里,痛不欲生。

最近,我的团队面临着一个巨大的挑战:构建一个白标银行解决方案,它需要支持多个客户品牌、不同的功能集,以及严格的监管要求。

我们的解决方案?Kotlin Multiplatform (KMP) 搭配 原子设计系统

这套架构让我们实现了 约 70% 的代码共享——包括 UI 状态和样式逻辑——同时还能利用 Jetpack Compose (Android) 和 SwiftUI (iOS) 渲染出像素级的完美原生 UI。来,看看我们是怎么做到的。

挑战:大规模白标化

我们不仅仅是在构建一个 App;我们是在构建一个能够生成几十个 App 的引擎。每个客户都有以下需求:

  • 独特的品牌形象: 颜色、字体、图标各不相同。
  • 特定的功能集: 比如有的想要加密货币功能,有的只需要银行卡功能。
  • 原生性能: 银行用户对卡顿的容忍度为零。

维护两套独立的代码库(Kotlin/Swift)会让我们的工程成本翻倍,还会拖慢更新速度。我们需要一个“单一事实来源”。

架构:配置驱动设计

标准的 KMP 非常适合共享业务逻辑(Domain 层、Platform 模块、本地/远程数据源)。但我们想玩把更大的。

我们问自己:能不能在不牺牲原生“渲染”的前提下,共享 UI 的“定义”?

我们采用了 原子设计 的方法,其中每个组件都由一个共享的 Config 对象来定义。

模式解析

我们不再在视图中编写 UI 逻辑,而是将其移至共享的 KMP 层。

1. Config(共享 commonMain)Config 是一个数据类,它保存了组件的“内容”和“意图”。它是唯一的事实来源。

// platform/kdesign/src/commonMain/kotlin/.../atom/button/KButton.kt

object KButton {  
    @Immutable  
    data classConfig(  
        val text: KStringDesc?= null,  
        val action: (() -> Unit)?= null,  
        val variant: Variant = Variant.Primary,  
        val state: State = State.Default,  
    ) {  
        // 从配置派生视觉样式  
        fun style(): Style = Style(variant, state)  
    }

    // Style 决定了它的外观颜色内边距等@Immutable  
    data classStyle(val variant: Variant, val state: State) {  
        val backgroundColor: KColorResource  
            get() = when (variant) {  
                Variant.Primary -> KThemeSpec.colors.buttonPrimaryBg  
                Variant.Secondary -> KThemeSpec.colors.buttonSecondaryBg  
            }  
    }  
}

2. 原生渲染 平台端现在变得很“傻”。它们不做逻辑决策,只是拿着 Config,用它们原生的框架进行渲染。

Android (Jetpack Compose):

@Composable  
fun PButton(config: KButton.Config) {  
    val style = config.style()  
      
    Button(  
        onClick = { config.action?.invoke() },  
        colors = ButtonDefaults.buttonColors(  
            containerColor = style.backgroundColor.toColor()   
        )  
    ) {  
        // 渲染内容  
    }  
}

iOS (SwiftUI):

struct PButtonView: View {  
    let config: KButton.Config  
    private let style: KButton.Style  
      
    init(config: KButton.Config) {  
        self.config = config  
        self.style = config.style()  
    }  
      
    var body: some View {  
        Button(action: { config.action?() }) {  
            // 渲染内容  
        }  
        .background(Color(style.backgroundColor.name))  
    }  
}

这确保了 逻辑不匹配成为不可能。如果一个按钮在表单无效时应该被禁用,这个逻辑就存在于共享的 ViewModel/Config 中。两个平台的更新步调完全一致。

用 JSON 驱动白标化

为了支持多个客户,我们使用了一套强大的资源生成流水线。

每个客户都有一个 config.json 文件。在构建过程中(使用 Gradle 任务),我们会解析这些文件以生成必要的资源。

  • 功能开关: 根据启用的功能动态加载 Gradle 模块(例如 includeBuild("features/crypto"))。
  • 主题化: 颜色和字体会自动生成到 Android XML/Compose 对象和 iOS .xcassets 中。

这将客户的接入时间从 2 周缩短到了约 2 天。这简直是老板们的福音!

技术栈与工具

为了让这套架构坚如磐石,我们依赖了一套现代化的技术栈:

  • Gradle 复合构建: 我们将 App 模块化为 16+ 个功能模块(Auth, Wallet, Transactions 等),实现了快速的增量构建和清晰的关注点分离。
  • SKIE: KMP 的游戏规则改变者。它极大地改善了 Swift 的互操作性,将 Kotlin 的 sealed classes 转换为 Swift 的 enums,并让 Coroutines Flow 在 SwiftUI 中更容易被消费。
  • XcodeGen: 我们不提交 .xcodeproj 文件。iOS 项目是即时生成的,确保文件引用永远不会与 Gradle 配置不同步。

成果展示

通过将 UI 的“大脑”移入 KMP,只把“皮肤”留给平台,我们取得了显著的成果:

  • 约 70% 代码共享: 业务逻辑、网络请求和设计系统定义只需编写一次。
  • 功能开发速度快 40%: 开发者只需专注于逻辑的一种实现。UI 实现仅仅是属性的映射。
  • 一致性: 设计系统由类型系统强制执行。你从物理上就无法创建一个不符合品牌指南的按钮。

功能解剖:跨平台扩展单一功能

为了理解我们是如何在 不牺牲原生 UI 质量 的前提下实现 约 70% 代码共享 的,我们需要看看 单一功能 是如何构建的。

我们不把功能仅仅看作“一个屏幕”。 每个功能都是一个 自包含的垂直切片,拥有明确的归属权,并通过工具和脚手架来强制执行。

功能的解剖学

为什么这很强大?

1. 业务逻辑保持纯粹

Domain 层 仅包含:

  • Models
  • Interfaces
  • Use cases

没有平台代码,没有 UI 假设。这些逻辑只需编写一次,随处共享。

2. 数据可替换

Data 层 实现领域契约:

  • APIs
  • Caching
  • Persistence
  • Mapping

你可以更改数据的获取方式,而无需触碰 UI 或业务规则。

3. UI 的“大脑”是共享的

Presentation 层 是真正发挥杠杆作用的地方:

  • 一个共享的 ViewModel
  • 事件驱动的状态机
  • 不可变的 UI configs

这一层决定了 UI 应该长什么样以及应该如何表现——只需一次。

4. 平台层轻薄且原生

Android 和 iOS 不包含逻辑。它们只是:

  • 渲染共享的 Config
  • 调用回调

Jetpack Compose 和 SwiftUI 保持了完全的原生性,但 逻辑不匹配变得不可能

核心洞察

我们不共享 UI 代码。我们共享 UI 意图

通过共享:

  • State(状态)
  • Events(事件)
  • Styling rules(样式规则)
  • Component structure(组件结构)

……并让每个平台进行原生渲染,我们获得了:

  • 像素级的原生 UI
  • 跨平台一致的行为
  • 大幅减少重复工作

这种严谨的功能结构是我们能够自信地扩展白标银行应用的关键——且没有牺牲性能、质量或开发人员的理智。

共享 ViewModel 如何驱动 UI 状态

该架构的一个核心创新在于 UI 行为和业务逻辑存在于共享的 ViewModel 中,它由状态机驱动,并完全由 UI 意图(事件和配置)驱动。这确保了:

  • 屏幕行为的 单一事实来源
  • 跨平台可预测的状态转换
  • 最大的可测试性

以下是使用示例功能的实际工作原理:

/**
 * Example feature showing:
 * - Config-driven screen contract (Config + Event)
 * - ConfigProvider that builds the initial Config (wires callbacks → events)
 * - ViewModel implemented with a StateMachine (event → state) + use case call
 *
 * Names are intentionally generic (no product/company prefixes).
 */

import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

// ------------------------------
// 1) Screen Contract (shared)
// ------------------------------
object SampleScreen {

    const val NAME = "SampleScreen"

    @Immutable
    sealed interface Event {
        data class OnInputChanged(val input: String) : Event
        data object OnSubmitClicked : Event
    }

    @Immutable
    data class Config(
        val inputField: InputFieldConfig?,
        val submitButton: ButtonConfig,
        val statusText: TextConfig?,
    )

    // Minimal, platform-agnostic component configs
    @Immutable
    data class InputFieldConfig(
        val text: String,
        val contentDescription: String,
        val onTextChange: (String) -> Unit,
    )

    @Immutable
    data class ButtonConfig(
        val title: String,
        val enabled: Boolean,
        val onClick: () -> Unit,
    )

    @Immutable
    data class TextConfig(val text: String)

    // Factory helpers to build component configs
    fun inputField(text: String, onEvent: (Event) -> Unit) = InputFieldConfig(
        text = text,
        contentDescription = "sample_input",
        onTextChange = { onEvent(Event.OnInputChanged(it)) }
    )

    fun submitButton(enabled: Boolean, onEvent: (Event) -> Unit) = ButtonConfig(
        title = "Submit",
        enabled = enabled,
        onClick = { onEvent(Event.OnSubmitClicked) }
    )
}

// ------------------------------
// 2) Config Provider (shared)
// ------------------------------
interface StateProvider<State, Event> {
    fun provide(onEvent: (Event) -> Unit): State
}

class SampleScreenConfigProvider : StateProvider<SampleScreen.Config, SampleScreen.Event> {
    override fun provide(onEvent: (SampleScreen.Event) -> Unit): SampleScreen.Config {
        val initialInput = ""
        return SampleScreen.Config(
            inputField = SampleScreen.inputField(text = initialInput, onEvent = onEvent),
            submitButton = SampleScreen.submitButton(
                enabled = initialInput.isNotBlank(),
                onEvent = onEvent
            ),
            statusText = null
        )
    }
}

// ------------------------------
// 3) Use Case (shared)
// ------------------------------
class SubmitSampleUseCase {
    suspend operator fun invoke(input: String): String {
        // Pretend we hit a repository/network and return a result string
        return "Submitted: $input"
    }
}

// ------------------------------
// 4) StateMachine (shared abstraction)
// ------------------------------
// Assume your project already has something like this.
// The key idea: mapEachEventToState { ... } and setState { ... }.
interface StateMachine<Event, State> {
    val state: kotlinx.coroutines.flow.StateFlow<State>
    fun sendEvent(event: Event)
    fun setState(reducer: State.() -> State)
    fun mapEachEventToState(handler: suspend (Event) -> Unit)
}

// Example factory you likely already have in your codebase.
class StateMachineWithProvider<Event, State>(
    initialStateProvider: StateProvider<State, Event>,
    private val coroutineScope: CoroutineScope,
) : StateMachine<Event, State> {
    private val _state = kotlinx.coroutines.flow.MutableStateFlow<State>(
        initialStateProvider.provide(::sendEvent)
    )
    override val state = _state

    private val events = kotlinx.coroutines.flow.MutableSharedFlow<Event>(extraBufferCapacity = 64)

    init {
        coroutineScope.launch {
            events.collect { /* no-op until mapEachEventToState is called */ }
        }
    }

    override fun sendEvent(event: Event) {
        events.tryEmit(event)
    }

    override fun setState(reducer: State.() -> State) {
        _state.value = _state.value.reducer()
    }

    override fun mapEachEventToState(handler: suspend (Event) -> Unit) {
        coroutineScope.launch {
            events.collect { handler(it) }
        }
    }
}

// ------------------------------
// 5) ViewModel (shared)
// ------------------------------
class SampleScreenViewModel(
    private val viewModelScope: CoroutineScope,
    private val configProvider: SampleScreenConfigProvider,
    private val submitUseCase: SubmitSampleUseCase,
    private val sm: StateMachine<SampleScreen.Event, SampleScreen.Config> =
        StateMachineWithProvider(initialStateProvider = configProvider, coroutineScope = viewModelScope)
) : ViewModel(), StateMachine<SampleScreen.Event, SampleScreen.Config> by sm {

    init {
        bindEvents()
    }

    private fun bindEvents() {
        mapEachEventToState { event ->
            when (event) {
                is SampleScreen.Event.OnInputChanged -> {
                    setState {
                        val updatedInput = event.input
                        copy(
                            inputField = SampleScreen.inputField(updatedInput, onEvent = ::sendEvent),
                            submitButton = SampleScreen.submitButton(
                                enabled = updatedInput.isNotBlank(),
                                onEvent = ::sendEvent
                            ),
                            statusText = null
                        )
                    }
                }

                SampleScreen.Event.OnSubmitClicked -> {
                    // Capture current input safely from state
                    val currentInput = state.value.inputField?.text.orEmpty()

                    // Optimistic UI: disable button + show loading text
                    setState {
                        copy(
                            submitButton = submitButton.copy(enabled = false),
                            statusText = SampleScreen.TextConfig("Submitting…")
                        )
                    }

                    viewModelScope.launch {
                        val resultText = submitUseCase(currentInput)

                        setState {
                            copy(
                                statusText = SampleScreen.TextConfig(resultText),
                                submitButton = submitButton.copy(enabled = currentInput.isNotBlank())
                            )
                        }
                    }
                }
            }
        }
    }
}

这里发生了什么?

✅ 共享 UI 契约

  • ViewModel 使用一个 屏幕契约:一个描述 UI 状态的 Config,以及代表用户操作的 Event 对象。
  • StateMachine 接口消费事件并暴露 Config 的 StateFlow。

✅ 事件驱动更新

  • 每个用户操作(OnInputChanged, OnSubmitClicked)都会映射到:
    • 一个状态更新(不可变配置)
    • 可能的业务逻辑执行(比如调用 use case)

这使得 ViewModel:

  • 确定性 —— 每个状态都是前一个状态 + 事件的纯函数
  • 可测试性 —— 你可以在单元测试中完全通过事件来驱动它
  • 平台无关 —— UI 渲染只是当前配置的投影

✅ 共享逻辑,原生渲染

平台只需观察状态流并 渲染 配置:

  • UI 层没有逻辑
  • 没有重复的事件处理
  • Android/iOS 行为之间没有偏差

这种模式是我们保持 行为单一实现 却每次都能交付 原生 UI 的支柱。

大规模测试:单元、UI 和 E2E 自动化

光有架构是不够的——它必须在大规模下被证明是正确的

从第一天起,测试就被视为 一等公民的架构关注点,而不是事后诸葛亮。因为我们的功能遵循严格、共享的结构,测试变得可预测且可重复。

1. 共享单元测试(安全网)

大部分逻辑都存在于共享的 KMP 层,这也是我们首先关注的地方。

对于每个功能,我们编写 共享 ViewModel 测试,用于:

  • 验证初始 UI 状态
  • 验证事件 → 状态转换
  • 断言业务规则和边缘情况
  • 运行一次,覆盖两个平台

因为 ViewModel 发出不可变的 UI 配置,测试变得很简单:

“给定这个事件,UI 配置是否按预期变化?”

这给了我们快速的反馈,并在 Bug 到达设备之前就捕获它们。

2. 原生 UI 测试(平台信心)

虽然逻辑是共享的,但渲染是原生的——所以我们仍然需要原生测试。

在 Android 上,我们使用:

  • Compose UI tests
  • Robot 风格的抽象以提高可读性

这些测试验证:

  • 屏幕渲染正确
  • 用户交互连接正确
  • 无障碍标识符稳定

UI 层保持轻薄,所以这些测试很稳定,很少破坏。

3. 端到端测试(真实用户流程)

最后,我们验证真正重要的东西:真实用户旅程

我们的 E2E 测试:

  • 像用户一样启动 App
  • 跨多个屏幕导航
  • 验证成功和失败路径

因为功能是模块化且配置驱动的,E2E 测试可以在不同客户和功能集之间清晰地组合。

为什么这行之有效?

这种分层测试策略之所以有效,是因为它反映了架构:

  • 共享逻辑 → 共享测试
  • 原生 UI → 原生 UI 测试
  • 关键流程 → E2E 自动化

结果是:

  • Bug 被早期捕获
  • 平台偏差最小化
  • 新的白标客户不会导致测试矩阵爆炸

在银行应用中——正确性、一致性和信任是不可妥协的——这种测试策略是我们能够快速行动 而不 搞砸一切的保障。

总结

Kotlin Multiplatform 通常被宣传为共享数据层的一种方式。然而,白标银行应用证明了 KMP 可以做得更多。

通过实施 配置驱动设计系统,我们在原生性能和跨平台效率之间架起了一座桥梁。我们不仅共享了代码;我们共享了用户界面的 架构,为未来的银行应用创建了一个可扩展的基础。

原文链接https://proandroiddev.com/beyond-shared-logic-building-a-whitelabel-app-with-kotlin-multiplatform-d220a0b196b2?source=search_post---------1-----------------------------------

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-02-08 21:03:13 HTTP/2.0 GET : https://f.mffb.com.cn/a/469176.html
  2. 运行时间 : 0.116494s [ 吞吐率:8.58req/s ] 内存消耗:4,547.51kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=72496629f92c9b23a857fea118389e5b
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000582s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000697s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000295s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.001866s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000504s ]
  6. SELECT * FROM `set` [ RunTime:0.000192s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000569s ]
  8. SELECT * FROM `article` WHERE `id` = 469176 LIMIT 1 [ RunTime:0.003060s ]
  9. UPDATE `article` SET `lasttime` = 1770555793 WHERE `id` = 469176 [ RunTime:0.021702s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 65 LIMIT 1 [ RunTime:0.001416s ]
  11. SELECT * FROM `article` WHERE `id` < 469176 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000491s ]
  12. SELECT * FROM `article` WHERE `id` > 469176 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000998s ]
  13. SELECT * FROM `article` WHERE `id` < 469176 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.008867s ]
  14. SELECT * FROM `article` WHERE `id` < 469176 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.005416s ]
  15. SELECT * FROM `article` WHERE `id` < 469176 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.004668s ]
0.118071s