Gone框架模块化改造之路

实战经验:Gone 框架模块化改造中的 go work 反思 本文分享了作者在 gone 框架模块化改造过程中对 Go 语言官方多模块开发工具 go work 的调研和使用体验。文章介绍了 go work 的基本功能和使用场景,深入分析了其在实际开发中存在的问题,特别是命令兼容性问题、版本管理混乱和 CI/CD 集成困难等。作者以 gone 框架的实际案例说明了为何最终放弃使用 go work 而选择回归传统的 go mod replace 方案,并提出了针对不同复杂度项目的实用建议。文章结论认为 go work 虽有用但功能有限,像"鸡肋"一样食之无味,弃之可惜,期 阅读详情

🚀 发现 gone-io/gone:一个优雅的 Go 依赖注入框架!💻 它让您的代码更简洁、更易测试。🔍 框架轻量却功能强大,完美平衡了灵活性与易用性。⭐ 如果您喜欢这个项目,请给我们点个星!🌟 您的支持是我们前进的动力!🤝 欢迎贡献代码或提出建议,一起让 gone 变得更好!👨‍💻 #golang #依赖注入 #开源 👉github.com/gone-io/gone
本文原地址:https://github.com/gone-io/goner/blob/main/docs/modules_update.md

一、改造背景

1. Gone框架的定位

Gone框架是一个轻量级的依赖注入框架,它通过简洁的API和注解式依赖声明,帮助开发者轻松构建模块化、可测试的应用程序。作为一个轻量级框架,Gone坚持"小而美"的设计理念,只提供必要的核心功能,避免过度臃肿。

2. 可插拔组件的设计

Gone框架提供了一系列可插拔的组件(称为Goner),涵盖了Web开发、数据库访问、配置管理、日志记录等多个方面:

  • gin:Web框架集成
  • gorm:ORM框架集成
  • viper:配置管理
  • redis:缓存服务
  • grpc:RPC服务
  • apollo/nacos:配置中心
  • zap:日志组件
  • 等等

这种设计让开发者可以根据实际需求选择所需组件,实现灵活组合,避免了"全家桶"式的强制依赖。

3. 原有架构的问题

改造前,Gone核心代码和所有Goner组件都在同一个仓库中管理,共享同一个Go module。这种架构存在以下问题:

  • 依赖膨胀:用户即使只需要某一个特定功能(如gin集成),也必须导入整个Gone模块,包含大量不需要的依赖
  • 版本管理困难:所有组件共享同一个版本号,无法针对单个组件进行独立版本迭代
  • 维护成本高:随着组件数量增加,单一仓库的维护难度急剧上升
  • 违背轻量级理念:过多的依赖与Gone框架轻量级的设计理念相矛盾

二、改造过程

1. 仓库分离

首先,我们将Goner组件库从原始的github.com/gone-io/gone仓库中分离出来,独立为一个新仓库:github.com/gone-io/goner。

这一步骤包括:

  • 创建新的Git仓库
  • 迁移相关代码,保留提交历史
  • 调整导入路径

2. 单仓库多模块(Mono Repo)改造

对github.com/gone-io/goner进行Mono Repo改造,将每个Goner组件作为独立的子模块管理:

github.com/gone-io/goner/
├── g/                  # 基础接口定义
├── gin/                # Gin框架集成
├── gorm/               # GORM框架集成
├── redis/              # Redis客户端集成
├── viper/              # Viper配置管理
├── apollo/             # Apollo配置中心
├── nacos/              # Nacos配置中心
├── zap/                # Zap日志组件
├── grpc/               # gRPC服务集成
├── cmux/               # 连接复用
├── schedule/           # 定时任务
├── tracer/             # 链路追踪
├── urllib/             # HTTP客户端
├── xorm/               # XORM框架集成
└── ...

每个子目录都是一个独立的Go模块,拥有自己的go.mod文件,可以独立版本化和发布。

3. 接口抽象与依赖解耦

为了解决组件间的相互依赖问题,我们采用了"依赖接口,不依赖实现"的设计原则:

  • 创建github.com/gone-io/goner/g子模块,用于定义所有公共接口
  • 所有组件都依赖于这个基础接口模块,而不是直接依赖其他组件
  • 通过接口抽象,实现了组件间的松耦合

例如,在g/tracer.go中定义了链路追踪相关接口,而不是直接依赖于tracer的具体实现:

// /g/tracer.go
package g

// Tracer 用于日志追踪,为同一调用链分配统一的traceId,方便日志追踪
type Tracer interface {

    //SetTraceId 为调用函数设置traceId。如果traceId为空字符串,将自动生成一个。
    //调用函数可以通过GetTraceId()方法获取traceId。
    SetTraceId(traceId string, fn func())

    //GetTraceId 获取当前goroutine的traceId
    GetTraceId() string
 
    //Go 启动一个新的goroutine,代替原生的`go func`,可以将traceId传递给新的goroutine
    Go(fn func())
}

三、存在问题及解决方案

1. Go项目单仓库多模块的版本管理

在Go模块系统中,单仓库多模块的版本管理是一个常见挑战。我们采用了以下策略:

子模块版本标签

我们为每个子模块设置独立的Git标签进行版本管理,确保依赖引用时能准确定位对应版本的代码:

<module>/<version>

例如:

  • gin/v1.0.0 - 表示gin模块的1.0.0版本
  • gorm/v0.2.1 - 表示gorm模块的0.2.1版本

这种方式允许每个子模块独立版本化,同时保持在同一个仓库中管理的便利性。

版本依赖管理

在子模块的go.mod文件中,通过replace指令处理本地模块间的依赖关系:

// 开发环境中使用本地路径
replace github.com/gone-io/goner/g => ../g

// 发布后,使用具体版本
require github.com/gone-io/goner/g v0.1.0

这确保了在开发过程中可以使用本地模块,而在发布后则使用特定版本的依赖。

2. Go项目单仓库多模块的单元测试

对于单仓库多模块中的单元测试,我们采用了分散测试、集中报告的策略:

分模块测试

每个子模块都有自己的测试套件,可以独立运行和验证:

cd gin && go test -v ./...
cd gorm && go test -v ./...
集中测试报告

通过GitHub Actions工作流(.github/workflows/go.yml)实现测试的自动化运行和报告合并:

- name: Run coverage
  run: find . -name go.mod -not -path "*/example/*" -not -path "*/examples/*" | xargs -n1 dirname | xargs -L1 bash -c 'cd "$0" && pwd && go test -race -coverprofile=coverage.txt -covermode=atomic ./... || exit 255' || echo "Tests failed"
- name: Merge coverage
  run: find . -name "coverage.txt" -exec cat {} \; > total_coverage.txt
- name: Upload coverage reports to Codecov
  uses: codecov/codecov-action@v5
  with:
      token: ${{ secrets.CODECOV_TOKEN }}
      files: ./total_coverage.txt

这个工作流会:

  1. 查找所有的go.mod文件(排除示例目录)
  2. 在每个模块目录中运行测试并生成覆盖率报告
  3. 合并所有覆盖率报告
  4. 将合并后的报告上传到Codecov进行分析

3. 组件间依赖问题

Goner组件之间存在相互依赖关系,这可能导致循环依赖或过度耦合。我们通过以下方式解决:

接口抽象

将所有共享接口抽象到github.com/gone-io/goner/g子模块中:

// g/cmux.go
package g

// Listener 定义了网络监听器接口
type Listener interface {
    // ...
}

// g/tracer.go
package g

// Tracer 定义了链路追踪接口
type Tracer interface {
    // ...
}
依赖注入

利用Gone框架的依赖注入机制,在运行时动态解析依赖关系:

type MyService struct {
    gone.Flag
    Logger g.Logger `gone:"*"`  // 依赖接口,而非具体实现
}
插件化设计

采用插件化设计,允许组件在运行时动态加载:

func main() {
    gone.
        Loads(
            zap.Load,     // 加载zap日志组件
            gin.Load,     // 加载gin组件
            gorm.Load,    // 加载gorm组件
        ).
        Run(func() {
            // 应用启动
        })
}

四、改造成果

通过这次模块化改造,Gone框架及其生态系统获得了显著改进:

  1. 更轻量的依赖:用户可以只引入所需的组件,大大减少不必要的依赖
  2. 灵活的版本管理:每个组件可以独立版本化,便于针对性升级和维护
  3. 更好的可测试性:组件间的松耦合使单元测试更易编写和维护
  4. 简化的开发流程:明确的接口定义和依赖关系,使开发新组件变得更加简单
  5. 更符合Go设计理念:遵循"小而美"的设计原则,每个模块专注于解决特定问题

五、未来展望

Gone框架的模块化改造是一个持续的过程,未来我们计划:

  1. 完善文档体系:为每个组件提供详细的使用文档和示例
  2. 扩展组件生态:开发更多实用组件,如消息队列、分布式锁等
  3. 性能优化:针对核心组件进行性能优化,提高整体效率
  4. 社区建设:鼓励社区贡献,建立更加活跃的开源生态

通过这些努力,我们希望Gone框架能够成为Go语言生态中一个轻量、高效、易用的依赖注入框架,为开发者提供更好的开发体验。

参考资源

Android 13 SystemUI 深度定制:打造沉浸式与受控界面的核心屏蔽方案 本文深入探讨了Android 13 SystemUI的深度定制技术,重点介绍了如何通过修改状态栏、导航栏和锁屏等核心组件,打造沉浸式与受控界面。文章提供了从基础架构理解到实战代码示例的完整方案,帮助开发者实现系统界面的高度定制化,适用于教育平板、数字标牌等特定场景。 阅读详情

相关推荐

科技中介服务机构想要提升服务效率,需要哪些数智工具支持?.docx

科技中介服务机构想要提升服务效率,需要哪些数智工具支持?

Gone框架介绍6 - 如何使用gone开发web服务

我在两年前实现了一个Golang的依赖注入框架,并且集成了gin、xorm、redis、cron、消息中间件等功能,自己觉得还挺好用的;之前一直没有时间写文档推广,现在"毕业了",有时间来完善完善了。github地址:https://github.com/gone-io/gone。如果有可能,请帮忙在github上点个 ⭐️?规范上,我们要求将服务的接口定义在。编写http接口,我们需要实现。方法中实现接口路由的挂载,如。文件:i_demo.go。服务的逻辑实现,放到。

大鹏的技术博客 1321

从零搭建Synopsys EDA环境:Ubuntu系统下的完整安装与避坑指南

本文详细介绍了在Ubuntu系统下从零搭建Synopsys EDA环境的完整流程,包括系统配置、安装文件准备、核心工具安装、License配置及常见问题排查。特别针对数字IC设计场景,提供了VCS、Verdi、Design Compiler等EDA Tools的安装避坑指南和性能优化建议,帮助开发者高效搭建稳定的开发环境。

weixin_33738982的博客 491

Gone框架介绍24 - Gone vs Spring,写给想转 Go 的 Java程序员

Spring和Gone在依赖模块导入、依赖注入、配置注入以及HTTP参数注入方面有不同的实现方式。Spring通过项目管理工具(如Maven或Gradle)引入依赖,并自动扫描加载依赖模块。依赖注入使用@Autowired注解,配置注入使用注解,HTTP参数注入则通过和注解实现。相对而言,Gone在函数中手动加载依赖模块,通过gone标签进行依赖和配置注入,并支持多种HTTP参数注入方式(如URL、Header、Body、Cookie、Form、Path等),灵活处理HTTP请求参数。

大鹏的技术博客 1995

MMSE (Minimum Mean Square Error)均衡原理讲解

MMSE是一种将接收数据的MSE(均方误差)最小化的模型。有了这句话,很多问题就会在你的脑海中蹦出来。 什么是均方误差?最小化MSE "的物理含义是什么? 等等。 让我们从一个我们现在已经非常熟悉的通道模型开始。(我希望你现在也熟悉下面的表达方式。) MMSE作为一种均衡器,是一种后处理算法,它可以帮助我们找出接收到的数据,使之尽可能地接近原始数据(传输数据)。简而言之,MMSE中最重要的步骤是找到一个如下图所示的矩阵G。如果我们假设没有噪声,这个[G]矩阵可以简单的作为信道矩阵(H^-1)的倒数。但

Celeste的博客 4万+

别再为GEE注册发愁了!用谷歌Cloud Project,5分钟搞定谷歌地球引擎账户(保姆级图文)

本文提供2024最新版谷歌地球引擎(GEE)账户注册的保姆级教程,通过谷歌Cloud Project快速完成注册,5分钟极速通关。详细解析注册前的准备、分步图解流程、常见误区及解决方案,帮助用户避开90%的注册陷阱,直达GEE核心功能区。

weixin_42557830的博客 201

Gone框架介绍31 - 使用`goner/zap`替代logrus默认实现,提升日志输出的效率

goner/zapgone是可以高效开发Web服务的Golang依赖注入框架。

大鹏的技术博客 967

Adams与Matlab联合仿真入门:从零搭建你的第一个动力学模型

本文为Adams与Matlab联合仿真新手提供了一份详尽的入门指南。文章从软件环境配置、版本兼容性检查讲起,逐步演示了如何在Adams中构建单摆动力学模型并定义输入输出接口,以及在Matlab/Simulink中搭建控制回路。通过一个完整的受控单摆实例,手把手教你完成从模型导出、模块生成到执行联合仿真的全流程,帮助你快速搭建并运行第一个联合仿真模型,实现动力学与控制系统的协同验证。

g5h6i7j的博客 700

Android 14 SystemUI 定制实战:锁屏、状态栏与QS面板改造指南

Android SystemUI 是 ROM 定制与设备开发中的核心模块,涵盖状态栏、锁屏、快速设置面板等关键界面。其定制原理基于 AOSP 源码模块化架构,通过修改 SystemUI 源码或使用 RRO overlay 实现界面与交互的深度调整。在 RK3576 等嵌入式平台上,SystemUI 定制能显著提升设备交互体验与品牌差异化。典型应用场景包括智能终端、工业平板及定制 ROM 开发。本文围绕 Android 14 SystemUI 定制,详细拆解锁屏布局、状态栏图标控制及 QS 磁贴重排的实操流程

weixin_34372728的博客 327

[STC32G144K246入门第六步]DAC驱动

本文摘要介绍了STC32G144微控制器内置的12位DAC模块特性与应用。该芯片集成双通道独立DAC,需通过内部运放缓冲后从P0.7/P5.0引脚输出,支持音频处理、波形生成等应用场景。重点阐述了DAC的R-2R梯形网络工作原理、关键性能指标(12位分辨率、转换速度等)及编程配置方法,包括频率设置、寄存器操作和运放引脚配置。文中还提供了32点正弦波表示例和初始化代码,强调参考电压稳定性对输出精度的重要影响。

sinat_58149788的博客 141

使用Qt Creator自带的Qt Designer构造UI界面流程总结

在本文中,我们将总结使用Qt Creator自带的Qt Designer构造UI界面的流程。在对话框中,您可以选择一个控件的信号,然后将其连接到另一个控件的槽。例如,您可以将按钮的点击信号连接到一个槽函数,以响应按钮的点击事件。右键单击窗口设计器中的空白区域,选择适当的布局管理器,如水平布局、垂直布局或网格布局。在Qt Designer中,您可以使用各种预定义的UI控件来设计应用程序的界面。现在,您可以在应用程序的源代码中使用生成的UI界面。在您的源文件中,包含相应的头文件,然后创建一个UI界面的实例。

cl6789的博客 1394

别再写重复代码了!Android购物车、订单、个人中心模块的封装与复用技巧

本文详细介绍了Android开发中购物车、订单和个人中心模块的高效封装与复用技巧,通过模块化设计和代码抽象,帮助开发者避免重复编写相似代码。文章提供了Kotlin实现的通用底部导航方案、Fragment基类设计、购物车业务逻辑处理以及订单状态机等实用技术方案,大幅提升电商类APP的开发效率。

weixin_33734785的博客 312

23、管状剪纸结构的研究与功能生成连杆机构的综合设计

本文研究了管状剪纸结构的压缩运动特性,通过剪纸技术将非刚性可折叠的Yoshimura和Kresling折纸结构转化为刚性可折叠结构,并提出径向可闭合剪纸(RC-kiri)构型。推导了位移与折叠角之间的数学关系,设计了带外部LET接头的RC-kiri模型,建立了力-位移理论模型并通过Abaqus进行有限元验证,制作了3D打印物理模型以测量直径变化。同时,研究了四杆和六杆功能生成连杆机构的综合设计方法,基于负闭合运动链和最小二乘法实现函数生成。结果表明理论、仿真与实验整体吻合良好,误差来源得到分析,为机器人、机

nokia的博客 167

ReVanced Patches深度解析:构建Android应用定制化的技术架构与实践

在移动应用生态日益封闭的今天,用户对于应用的控制权需求日益增长。ReVanced Patches项目应运而生,为Android应用提供了模块化的补丁系统,让用户能够重新掌控自己的应用体验。这个开源项目通过精巧的技术架构,实现了对数十款流行应用的深度定制,从广告屏蔽到功能增强,从界面美化到隐私保护,为Android用户提供了前所未有的应用自由度。 ## 架构设计:模块化补丁系统的技术实现 ReV

gitblog_00348的博客 378

从0到1打造一台机器人走起来

1)本体机械结构设计,依据人体生物学和生物力学知识,结合机器人运动时各关节扭矩需求,进行合理选用电机,比如胯部需要大扭矩,腿部惯量小等2)硬件拓扑上,分层架构的信号层和功率协同工作,高带宽,低延时,硬实时3)步态规划设计与验证环节实机回溯和部署过程:Real2Sim,Sim2Sim,Sim2Real。

weixin_43163941的博客 244

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Stisla实战:基于Bootstrap 4的后台模板快速开发指南

后台管理系统前端开发往往面临重复劳动和工期紧张的双重压力,而基于成熟模板进行二次开发是高效解法。Bootstrap 4作为经典CSS框架,提供了丰富的组件与响应式布局,结合Sass变量机制可实现主题的灵活定制。Stisla正是这样一套构建于Bootstrap 4之上的开源后台模板,内置百余个页面,并整合Gulp自动化构建流程,帮助开发者快速搭建出工程化、可维护的管理系统。无论接私活还是企业项目,从登录页到数据报表都能直接复用,再通过自定义Sass变量与布局类名,即可匹配不同品牌的视觉需求。文章详述了Stis

weixin_33827731的博客 342

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

如何用 SHAP 解释行星表面特征分类模型的决策过程:一份完整指南

探测器传回一批地表影像,地面训练的 CNN 把其中几张标记成了“撞击坑”。可这些图像里,究竟是哪些像素决定了分类结果?拿着模型输出做决策的科学家,没法接受一个无法解释的黑箱。SHAP(SHapley Additive exPlanations)解决的就是这个问题:它计算每个输入特征对模型输出的贡献,把“模型这么判断”变成“模型看到了这些细节才判断”。 ## 🎯 项目定位:让黑箱模型变成玻璃箱

gitblog_00274的博客 119

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Fundamentals of Software Architecture 2nd‑ed(2025)读书笔记

架构不是代码、框架,也不是静态文档;架构是一组高影响力设计决策。。

Maolei_NyaRu_的博客 391

ExoPlayer UI自定义实战:手把手教你打造个性化播放界面(附完整代码)

本文详细介绍了如何通过ExoPlayer进行UI自定义,打造个性化播放界面。从理解ExoPlayer的UI架构体系到实战自定义PlayerView布局和深度定制控制面板,再到高级功能实现和性能优化,手把手教你完成全流程开发,并附完整代码示例。

weixin_29230773的博客 286

安卓设计模式实战:从原理到性能优化

设计模式是面向对象编程的核心思想,通过封装变化点来提高代码复用性和扩展性。在安卓开发中,经典设计模式需要结合平台特性进行改造,如LiveData对观察者模式的生命周期感知优化。合理运用单例模式、建造者模式、适配器模式等可以解决视图绑定、异步任务等典型场景问题,同时需注意内存泄漏和线程安全等性能陷阱。通过Kotlin语言特性和Jetpack组件,现代安卓开发能更简洁地实现策略模式、状态模式等架构设计,提升RecyclerView等核心组件的渲染效率。掌握设计模式的安卓最佳实践,是构建可维护、高性能应用的关键。

weixin_29189373的博客 341

电气工程毕业论文题目【415个】

电气工程毕业论文题目

坷拉博士 5814

ButterKnife到ViewBinding的迁移指南与最佳实践

在Android开发中,视图绑定技术经历了从ButterKnife到ViewBinding的演进。ButterKnife曾通过编译时注解极大简化了findViewById操作,但随着AGP升级和R8编译器的普及,其兼容性问题日益凸显。ViewBinding作为官方推荐的替代方案,提供了类型安全和空安全等优势,同时避免了运行时开销。本文重点解析如何从ButterKnife平滑迁移到ViewBinding,涵盖环境配置、代码转换、性能优化等关键步骤,并分享与Jetpack组件整合的进阶技巧,帮助开发者构建更健壮

weixin_30613727的博客 364

基于USB主机与Qwiic生态的智能桌面留言牌DIY指南

USB主机(USB Host)是嵌入式系统中连接标准外设的关键技术,它允许微控制器作为主机,直接识别和管理USB键盘、鼠标等设备。其原理基于USB协议栈,通过枚举设备、配置端点来实现数据通信。这项技术的价值在于极大地扩展了嵌入式系统的交互能力,使其能够利用成熟、廉价的消费级外设。在物联网、智能终端和人机交互等应用场景中,USB主机功能简化了输入方案。本文聚焦于利用MicroMod SAMD51的USB主机功能,结合即插即用的Qwiic生态系统,构建一个可通过USB键盘输入、在LCD屏上显示信息的智能桌面留言

weixin_30567225的博客 386

Django集成Web Push实现无轮询实时通知

Web Push是一种基于W3C标准的浏览器端推送技术,其核心原理是通过Service Worker监听push事件,并由Push API完成加密消息投递。相比传统AJAX轮询或WebSocket长连接,它具备零服务端连接占用、用户离线仍可触达、跨浏览器兼容等显著技术优势。在Django工程实践中,该方案能有效降低60%以上服务器QPS,提升用户回访率,适用于订单提醒、审批通知、课程开课等典型业务场景。本文聚焦Django后端与Service Worker的协同集成,详解VAPID认证、Subscripti

weixin_33698043的博客 479

Mythos解析:Anthropic推理增强架构与可控AI实践

大语言模型推理的可控性与确定性正成为企业级AI落地的核心瓶颈。传统方案依赖后处理、RAG或prompt工程来弥补幻觉、逻辑断裂与合规越界等问题,但效果滞后且维护成本高。Mythos作为Anthropic推出的推理增强架构,通过分层解耦设计,在运行时嵌入Reasoning State Orchestrator(RSO)与Constraint Validation Engine(CVE),实现意图可验证、执行可复现、场景可管控。它不提升通用能力上限,而是将高风险领域(如法律、医疗、金融)的错误拦截率从61.7%

weixin_38166852的博客 498

PHP老系统无痛重构:不中断业务前提下完成架构升级的工程化方法论》

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> div{ width: 300px; height: 200px; border:5px solid gray; margin:30px auto; } .box1 { backgrou

qq_37805832的博客 850
上一篇: Gone v2 配置管理2:使用 goner/viper/remote链接远程的配置中心,支持etcd、consul、firestore、nats
下一篇: 实战经验:Gone 框架模块化改造中的 go work 反思
dapeng-大鹏
博客等级 码龄16年 1349粉丝 116原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dapeng-大鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值