广告
返回顶部
首页 > 资讯 > 后端开发 > GO >Go语言状态机的实现
  • 639
分享到

Go语言状态机的实现

Go语言状态机 2023-03-09 17:03:47 639人浏览 泡泡鱼
摘要

目录一、状态机二、代码1. database包2. fsm包3. order包4. main包三、个人总结一、状态机 1. 定义 有限状态机(Finite-state Machine

一、状态机

1. 定义

有限状态机(Finite-state Machine, FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

2. 组成要素

  • 现态(src state):事务当前所处的状态。
  • 事件(event):事件就是执行某个操作的触发条件,当一个事件被满足,将会触发一个动作,或者执行一次状态的迁移。
  • 动作(action):事件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当事件满足后,也可以不执行任何动作,直接迁移到新状态。
  • 次态(dst state):事件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
  • 状态流转(transition):事物从现态转为次态的整个过程。

3. 优点

  • 代码抽象:将业务流程进行抽象和结构化,将复杂的状态转移图,分割成相邻状态的最小单元。这样相当于搭建乐高积木,在这套机制上可以组合成复杂的状态转移图,同时隐藏了系统的复杂度。
  • 简化流程:业务rd只需要关注当前操作的业务逻辑(状态流转过程中的业务回调函数),极大的解耦了状态和业务。
  • 易扩展:在新增状态或事件时,无需修改原有的状态流转逻辑,直接建立新的状态转移链路即可。
  • 业务建模:通过最小粒度的相邻状态拼接,最终组成了业务的整体graph。

二、代码

假设我们有要实现一个订单下单功能,上图是订单状态的流转图,方框为订单的状态,箭头旁的文字为事件。

1. database包

package database

import "fmt"

// DB 模拟数据库对象
type DB struct {
}

// Transaction 模拟事务
func (db *DB) Transaction(fun func() error) error {
    fmt.Println("事务执行开始。")
    err := fun()
    fmt.Println("事务执行结束。")
    return err
}

// Order 订单
type Order struct {
    ID    int64 // 主键ID
    State int   // 状态
}

type OrderList []*Order

// 查询所有订单
func ListAllOrder() (OrderList, error) {
    orderList := OrderList{
        &Order{1, 0},
        &Order{2, 1},
        &Order{2, 2},
    }
    return orderList, nil
}

// UpdateOrderState 更新订单状态
func UpdateOrderState(curOrder *Order, srcState int, dstState int) error {
    if curOrder.State == srcState {
        curOrder.State = dstState
    }
    fmt.Printf("更新id为 %v 的订单状态,从现态[%v]到次态[%v]\n", curOrder.ID, srcState, dstState)
    return nil
}

用来模拟数据库的操作,如数据库事务、查询所有订单、更新订单状态等。

2. fsm包

package fsm

import (
    "fmt"
    "reflect"
    "zuzhiang/database"
)

// FSMState 状态机的状态类型
type FSMState int

// FSMEvent 状态机的事件类型
type FSMEvent string

// FSMTransitionMap 状态机的状态转移图类型,现态和事件一旦确定,次态和动作就唯一确定
type FSMTransitionMap map[FSMState]map[FSMEvent]FSMDstStateAndAction

// FSMTransitionFunc 状态机的状态转移函数类型
type FSMTransitionFunc func(params map[string]interface{}, srcState FSMState, dstState FSMState) error

// FSMDstStateAndAction 状态机的次态和动作
type FSMDstStateAndAction struct {
    DstState FSMState  // 次态
    Action   FSMAction // 动作
}

// FSMAction 状态机的动作
type FSMAction interface {
    Before(bizParams map[string]interface{}) error                   // 状态转移前执行
    Execute(bizParams map[string]interface{}, tx *database.DB) error // 状态转移中执行
    After(bizParams map[string]interface{}) error                    // 状态转移后执行
}

// FSM 状态机,元素均为不可导出
type FSM struct {
    transitionMap  FSMTransitionMap  // 状态转移图
    transitionFunc FSMTransitionFunc // 状态转移函数
}

// CreateNewFSM 创建一个新的状态机
func CreateNewFSM(transitionFunc FSMTransitionFunc) *FSM {
    return &FSM{
        transitionMap:  make(FSMTransitionMap),
        transitionFunc: transitionFunc,
    }
}

// SetTransitionMap 设置状态机的状态转移图
func (fsm *FSM) SetTransitionMap(srcState FSMState, event FSMEvent, dstState FSMState, action FSMAction) {
    if int(srcState) < 0 || len(event) <= 0 || int(dstState) < 0 {
        panic("现态|事件|次态非法。")
        return
    }
    transitionMap := fsm.transitionMap
    if transitionMap == nil {
        transitionMap = make(FSMTransitionMap)
    }
    if _, ok := transitionMap[srcState]; !ok {
        transitionMap[srcState] = make(map[FSMEvent]FSMDstStateAndAction)
    }
    if _, ok := transitionMap[srcState][event]; !ok {
        dstStateAndAction := FSMDstStateAndAction{
            DstState: dstState,
            Action:   action,
        }
        transitionMap[srcState][event] = dstStateAndAction
    } else {
        fmt.Printf("现态[%v]+事件[%v]+次态[%v]已定义过,请勿重复定义。\n", srcState, event, dstState)
        return
    }
    fsm.transitionMap = transitionMap
}

// Push 状态机的状态迁移
func (fsm *FSM) Push(tx *database.DB, params map[string]interface{}, currentState FSMState, event FSMEvent) error {
    // 根据现态和事件从状态转移图获取次态和动作
    transitionMap := fsm.transitionMap
    events, eventExist := transitionMap[currentState]
    if !eventExist {
        return fmt.Errorf("现态[%v]未配置迁移事件", currentState)
    }
    dstStateAndAction, ok := events[event]
    if !ok {
        return fmt.Errorf("现态[%v]+迁移事件[%v]未配置次态", currentState, event)
    }
    dstState := dstStateAndAction.DstState
    action := dstStateAndAction.Action

    // 执行before方法
    if action != nil {
        fsmActionName := reflect.ValueOf(action).String()
        fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].before\n", currentState, event, dstState, fsmActionName)
        if err := action.Before(params); err != nil {
            return fmt.Errorf("现态[%v]+迁移事件[%v]->次态[%v]失败, [%v].before, err: %v", currentState, event, dstState, fsmActionName, err)
        }
    }

    // 事务执行execute方法和transitionFunc
    if tx == nil {
        tx = new(database.DB)
    }
    transactionErr := tx.Transaction(func() error {
        fsmActionName := reflect.ValueOf(action).String()
        fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].execute\n", currentState, event, dstState, fsmActionName)
        if action != nil {
            if err := action.Execute(params, tx); err != nil {
                return fmt.Errorf("状态转移执行出错:%v", err)
            }
        }

        fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], transitionFunc\n", currentState, event, dstState)
        if err := fsm.transitionFunc(params, currentState, dstState); err != nil {
            return fmt.Errorf("执行状态转移函数出错: %v", err)
        }
        return nil
    })
    if transactionErr != nil {
        return transactionErr
    }

    // 执行after方法
    if action != nil {
        fsmActionName := reflect.ValueOf(action).String()
        fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].after\n", currentState, event, dstState, fsmActionName)
        if err := action.After(params); err != nil {
            return fmt.Errorf("现态[%v]+迁移事件[%v]->次态[%v]失败, [%v].before, err: %v", currentState, event, dstState, fsmActionName, err)
        }
    }
    return nil
}

状态机包含的元素有两个:状态转移图和状态转移函数,为了防止包外直接调用,这两个元素都设为了不可导出的。状态转移图说明了状态机的状态流转情况,状态转移函数定义了在状态转移的过程中需要做的事情,在创建状态时指定,如更新数据库实体(order)的状态。

而状态转移图又包含现态、事件、次态和动作,一旦现态和事件确定,那么状态流转的唯一次态和动作就随之确定。

状态机的动作又包含三个:Before、Execute和After。Before操作在是事务前执行,由于此时没有翻状态,所以该步可能会被重复执行。Execute操作是和状态转移函数在同一事务中执行的,同时成功或同时失败。After操作是在事务后执行,因为在执行前状态已经翻转,所以最多会执行一次,在业务上允许执行失败或未执行。

状态机的主要方法有两个,SetTransitionMap方法用来设置状态机的状态转移图,Push方法用来根据现态和事件推动状态机进行状态翻转。Push方法中会先执行Before动作,再在同一事务中执行Execute动作和状态转移函数,最后执行After动作。在执行Push方法的时候会将params参数传递给状态转移函数。

3. order包

(1) order

package order

import (
    "fmt"
    "zuzhiang/database"
    "zuzhiang/fsm"
)

var (
    // 状态
    StateOrderInit          = fsm.FSMState(0) // 初始状态
    StateOrderToBePaid      = fsm.FSMState(1) // 待支付
    StateOrderToBeDelivered = fsm.FSMState(2) // 待发货
    StateOrderCancel        = fsm.FSMState(3) // 订单取消
    StateOrderToBeReceived  = fsm.FSMState(4) // 待收货
    StateOrderDone          = fsm.FSMState(5) // 订单完成

    // 事件
    EventOrderPlace      = fsm.FSMEvent("EventOrderPlace")      // 下单
    EventOrderPay        = fsm.FSMEvent("EventOrderPay")        // 支付
    EventOrderPayTimeout = fsm.FSMEvent("EventOrderPayTimeout") // 支付超时
    EventOrderDeliver    = fsm.FSMEvent("EventOrderDeliver")    // 发货
    EventOrderReceive    = fsm.FSMEvent("EventOrderReceive")    // 收货
)

var orderFSM *fsm.FSM

// orderTransitionFunc 订单状态转移函数
func orderTransitionFunc(params map[string]interface{}, srcState fsm.FSMState, dstState fsm.FSMState) error {
    // 从params中解析order参数
    key, ok := params["order"]
    if !ok {
        return fmt.Errorf("params[\"order\"]不存在。")
    }
    curOrder := key.(*database.Order)
    fmt.Printf("order.ID: %v, order.State: %v\n", curOrder.ID, curOrder.State)

    // 订单状态转移
    if err := database.UpdateOrderState(curOrder, int(srcState), int(dstState)); err != nil {
        return err
    }
    return nil
}

// Init 状态机的状态转移图初始化
func Init() {
    orderFSM = fsm.CreateNewFSM(orderTransitionFunc)
    orderFSM.SetTransitionMap(StateOrderInit, EventOrderPlace, StateOrderToBePaid, PlaceAction{})                  // 初始化+下单 -> 待支付
    orderFSM.SetTransitionMap(StateOrderToBePaid, EventOrderPay, StateOrderToBeDelivered, PayAction{})             // 待支付+支付 -> 待发货
    orderFSM.SetTransitionMap(StateOrderToBePaid, EventOrderPayTimeout, StateOrderCancel, nil)                     // 待支付+支付超时 -> 订单取消
    orderFSM.SetTransitionMap(StateOrderToBeDelivered, EventOrderDeliver, StateOrderToBeReceived, DeliverAction{}) // 待发货+发货 -> 待收货
    orderFSM.SetTransitionMap(StateOrderToBeReceived, EventOrderReceive, StateOrderDone, ReceiveAction{})          // 待收货+收货 -> 订单完成
}

// ExecOrderTask 执行订单任务,推动状态转移
func ExecOrderTask(params map[string]interface{}) error {
    // 从params中解析order参数
    key, ok := params["order"]
    if !ok {
        return fmt.Errorf("params[\"order\"]不存在。")
    }
    curOrder := key.(*database.Order)

    // 初始化+下单 -> 待支付
    if curOrder.State == int(StateOrderInit) {
        if err := orderFSM.Push(nil, params, StateOrderInit, EventOrderPlace); err != nil {
            return err
        }
    }
    // 待支付+支付 -> 待发货
    if curOrder.State == int(StateOrderToBePaid) {
        if err := orderFSM.Push(nil, params, StateOrderToBePaid, EventOrderPay); err != nil {
            return err
        }
    }
    // 待支付+支付超时 -> 订单取消
    if curOrder.State == int(StateOrderToBePaid) {
        if err := orderFSM.Push(nil, params, StateOrderToBePaid, EventOrderPayTimeout); err != nil {
            return err
        }
    }
    // 待发货+发货 -> 待收货
    if curOrder.State == int(StateOrderToBeDelivered) {
        if err := orderFSM.Push(nil, params, StateOrderToBeDelivered, EventOrderDeliver); err != nil {
            return err
        }
    }
    // 待收货+收货 -> 订单完成
    if curOrder.State == int(StateOrderToBeReceived) {
        if err := orderFSM.Push(nil, params, StateOrderToBeReceived, EventOrderReceive); err != nil {
            return err
        }
    }
    return nil
}

order包中做的事情主要有:

  • 定义订单状态机的状态和事件;
  • 创建一个状态机,并设置状态转移函数和状态转移图;
  • 执行订单任务,推动状态转移。

(2) order_action_place

package order

import (
    "fmt"
    "zuzhiang/database"
)

type PlaceAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver PlaceAction) Before(bizParams map[string]interface{}) error {
    fmt.Println("执行下单的Before方法。")
    return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver PlaceAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
    fmt.Println("执行下单的Execute方法。")
    return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver PlaceAction) After(bizParams map[string]interface{}) error {
    fmt.Println("执行下单的After方法。")
    return nil
}

(2) ~ (5)是订单不同动作的声明和实现。

(3) order_action_pay

package order

import (
    "fmt"
    "zuzhiang/database"
)

type PayAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver PayAction) Before(bizParams map[string]interface{}) error {
    fmt.Println("执行支付的Before方法。")
    return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver PayAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
    fmt.Println("执行支付的Execute方法。")
    return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver PayAction) After(bizParams map[string]interface{}) error {
    fmt.Println("执行支付的After方法。")
    return nil
}

(4) order_action_deliver

package order

import (
    "fmt"
    "zuzhiang/database"
)

type DeliverAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver DeliverAction) Before(bizParams map[string]interface{}) error {
    fmt.Println("执行发货的Before方法。")
    return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver DeliverAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
    fmt.Println("执行发货的Execute方法。")
    return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver DeliverAction) After(bizParams map[string]interface{}) error {
    fmt.Println("执行发货的After方法。")
    return nil
}

(5) order_action_receive

package order

import (
    "fmt"
    "zuzhiang/database"
)

type ReceiveAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver ReceiveAction) Before(bizParams map[string]interface{}) error {
    fmt.Println("执行收货的Before方法。")
    return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver ReceiveAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
    fmt.Println("执行收货的Execute方法。")
    return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver ReceiveAction) After(bizParams map[string]interface{}) error {
    fmt.Println("执行收货的After方法。")
    return nil
}

4. main包

package main

import (
    "fmt"
    "zuzhiang/database"
    "zuzhiang/order"
)

func main() {
    order.Init()
    orderList, dbErr := database.ListAllOrder()
    if dbErr != nil {
        return
    }
    for _, curOrder := range orderList {
        params := make(map[string]interface{})
        params["order"] = curOrder
        if err := order.ExecOrderTask(params); err != nil {
            fmt.Printf("执行订单任务出错:%v\n", err)
        }
        fmt.Println("\n\n")
    }
}

最后在main包里先初始化一个订单状态机,查询所有订单,并使用状态机执行订单任务,推动订单状态转移。注意多个订单可以用同一个状态机来进行状态的迁移。

三、个人总结

为什么要使用状态机,我想主要是它可以对一个复杂的业务流程进行模块化拆分,使得代码更为易读。并且扩展性更好,如果后续有新状态加入,只需要在原来的基础上进行扩展即可,甚至不需要了解整个业务流程。

其次,它将数据库实体的状态流转进行了模范化,避免了不同的开发人员在写更新数据库实体状态代码时可能导致的问题。

到此这篇关于Go语言状态机的实现的文章就介绍到这了,更多相关Go语言状态机内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

您可能感兴趣的文档:

--结束END--

本文标题: Go语言状态机的实现

本文链接: https://www.lsjlt.com/news/199145.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Go语言状态机的实现
    目录一、状态机二、代码1. database包2. fsm包3. order包4. main包三、个人总结一、状态机 1. 定义 有限状态机(Finite-state machine...
    99+
    2023-03-09
    Go语言状态机
  • Go语言状态机如何实现
    这篇文章主要介绍“Go语言状态机如何实现”,在日常操作中,相信很多人在Go语言状态机如何实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言状态机如何实现”的疑惑有所帮助!接下来,请跟着小编一起来学习吧...
    99+
    2023-07-05
  • C语言实现进程5状态模型的状态机
    目录前言什么是状态机定义举例四大概念状态机的应用进程5状态模型实现前言 状态机在实际工作开发中应用非常广泛,在刚进入公司的时候,根据公司产品做流程图的时候,发现自己经常会漏了这样或那...
    99+
    2022-11-13
    C语言 进程5状态模型 状态机 C语言 进程5状态模型 C语言 状态机
  • 一文详解Go语言中的有限状态机FSM
    目录1、FSM简介1.1 有限状态机的定义1.2 Go中的FSM2、github.com/looplab/fsm 如何使用2.1 fsm 基础使用2.2 fsm 中 Action 何...
    99+
    2023-05-17
    Go语言有限状态机FSM Go 有限状态机 Go FSM
  • Go 语言现状是怎样的
    这篇文章将为大家详细讲解有关Go 语言现状是怎样的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。一、Go 开发者数量 & 所处地区全球大约有 110...
    99+
    2022-10-19
  • Go语言如何实现多态 
    这篇“Go语言如何实现多态 ”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Go语言如何实现多态 ”文章吧...
    99+
    2023-06-30
  • C语言中的状态机是什么
    本篇内容主要讲解“C语言中的状态机是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C语言中的状态机是什么”吧! 前言本文不是关于软件状态机的最佳设计分解实践的教程。我将重点关注状态...
    99+
    2023-06-15
  • 深入理解Go语言实现多态
    目录多态是什么Go语言多态举例总结多态是什么 相信学过Java这种面向对象语言的同学对于多态来说都不陌生,在代码执行的时候,能够根据子类的类型去执行子类当中的方法。多态是指代码可以根...
    99+
    2022-11-13
  • Go语言实现的语言有哪些
    这篇文章主要讲解了“Go语言实现的语言有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Go语言实现的语言有哪些”吧!01 Go+这是国内七牛老大许式伟主导的,对 Go 语言的扩展,专为数...
    99+
    2023-06-15
  • 浅谈Go语言多态的实现与interface使用
    目录一、多态的含义二、抽象类与接口三、Golang中的接口四、总结一、多态的含义 对于Java或者是C++而言,我们在使用变量的时候,变量的类型是明确的。但是如果我们希望它可以宽松一...
    99+
    2022-11-12
  • Go语言做爬虫状态码返回418的问题解决
    目录背景原因分析代码部分背景  在使用Go语言做爬虫时,使用http.Get(url)去获取网页内容,状态码返回404,Body体为空。 原因分析  http.Get(ur...
    99+
    2022-06-07
    爬虫 GO go语言
  • Go语言Grpc Stream的实现
    目录Stream GrpcStream Grpc演示BookListStreamCreateBookStreamFindBookByIdStreamStream Grpc 在我们单次...
    99+
    2022-11-13
  • 上门做菜系统的Go语言开发:如何实现订单状态跟踪功能?
    上门做菜系统的Go语言开发:如何实现订单状态跟踪功能?随着生活水平的提高,越来越多的人开始选择在家里点外卖或上门做菜。上门做菜服务为忙碌的人们提供了方便和舒适的用餐选择。在开发上门做菜系统时,一个重要的功能是订单状态的跟踪。本文将讨论如何使...
    99+
    2023-11-01
    Go语言开发 关键词:上门做菜系统 订单状态跟踪
  • 怎么在Go语言中实现锁机制
    本文小编为大家详细介绍“怎么在Go语言中实现锁机制”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在Go语言中实现锁机制”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。Go语言的锁在Go语言中,最常用的锁是互斥...
    99+
    2023-07-06
  • go指的是动态语言吗
    这篇文章将为大家详细讲解有关go指的是动态语言吗,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。go不是动态语言,而是静态类型语言。GO是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能...
    99+
    2023-06-14
  • Java状态机怎么实现
    今天小编给大家分享一下Java状态机怎么实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。假设我们有两个状态这个状态转换非常...
    99+
    2023-07-06
  • Go语言中的UTF-8实现
    计算机刚诞生的时候,计算机内的字符可以全部由 ASCII 来表示,ASCII 字符的长度是 7 位,可以表示 128 个字符,对于美国等国家来说是够了,但是对于世界上的其他国家...
    99+
    2022-06-07
    GO go语言
  • 怎么解决Go语言做爬虫状态码返回418的问题
    这篇文章主要介绍怎么解决Go语言做爬虫状态码返回418的问题,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!背景 在使用Go语言做爬虫时,使用http.Get(url)去获取网页内容,状态码返回404,Body体为空。...
    99+
    2023-06-22
  • go语言实现mqtt协议的实践
    目录一、什么是MQTT二、Go语言MQTT服务器Broker的搭建三、Go客户端访问简单API一、什么是MQTT MQTT(Message Queuing Telemetry Tra...
    99+
    2022-11-12
  • Go语言单向通道的实现
    目录单向通道的声明格式单向通道的使用例子 time包中的单向通道关闭 channelGo语言的类型系统提供了单方向的 channel 类型,顾名思义,单向 channel ...
    99+
    2023-05-14
    Go语言单向通道 Go单行道
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作