本文整理汇总了Golang中github.com/cockroachdb/cockroach/storage/engine.Engine类的典型用法代码示例。如果您正苦于以下问题:Golang Engine类的具体用法?Golang Engine怎么用?Golang Engine使用的例子?那么恭喜您, 这里精选的类代码示例或许可以为您提供帮助。
在下文中一共展示了Engine类的20个代码示例,这些例子默认根据受欢迎程度排序。您可以为喜欢或者感觉有用的代码点赞,您的评价将有助于我们的系统推荐出更棒的Golang代码示例。
示例1: append
// append the given entries to the raft log.
func (r *Replica) append(batch engine.Engine, entries []raftpb.Entry) error {
if len(entries) == 0 {
return nil
}
for _, ent := range entries {
err := engine.MVCCPutProto(batch, nil, keys.RaftLogKey(r.RangeID, ent.Index),
roachpb.ZeroTimestamp, nil, &ent)
if err != nil {
return err
}
}
lastIndex := entries[len(entries)-1].Index
prevLastIndex := atomic.LoadUint64(&r.lastIndex)
// Delete any previously appended log entries which never committed.
for i := lastIndex + 1; i <= prevLastIndex; i++ {
err := engine.MVCCDelete(batch, nil,
keys.RaftLogKey(r.RangeID, i), roachpb.ZeroTimestamp, nil)
if err != nil {
return err
}
}
// Commit the batch and update the last index.
if err := setLastIndex(batch, r.RangeID, lastIndex); err != nil {
return err
}
batch.Defer(func() {
atomic.StoreUint64(&r.lastIndex, lastIndex)
})
return nil
}
开发者ID:ming-hai,项目名称:cockroach,代码行数:33,代码来源:replica_raftstorage.go
示例2: newRangeDataIterator
func newRangeDataIterator(d *proto.RangeDescriptor, e engine.Engine) *rangeDataIterator {
// The first range in the keyspace starts at KeyMin, which includes the node-local
// space. We need the original StartKey to find the range metadata, but the
// actual data starts at LocalMax.
dataStartKey := d.StartKey
if d.StartKey.Equal(proto.KeyMin) {
dataStartKey = keys.LocalMax
}
ri := &rangeDataIterator{
ranges: []keyRange{
{
start: engine.MVCCEncodeKey(keys.MakeKey(keys.LocalRangeIDPrefix, encoding.EncodeUvarint(nil, uint64(d.RangeID)))),
end: engine.MVCCEncodeKey(keys.MakeKey(keys.LocalRangeIDPrefix, encoding.EncodeUvarint(nil, uint64(d.RangeID+1)))),
},
{
start: engine.MVCCEncodeKey(keys.MakeKey(keys.LocalRangePrefix, encoding.EncodeBytes(nil, d.StartKey))),
end: engine.MVCCEncodeKey(keys.MakeKey(keys.LocalRangePrefix, encoding.EncodeBytes(nil, d.EndKey))),
},
{
start: engine.MVCCEncodeKey(dataStartKey),
end: engine.MVCCEncodeKey(d.EndKey),
},
},
iter: e.NewIterator(),
}
ri.iter.Seek(ri.ranges[ri.curIndex].start)
ri.advance()
return ri
}
开发者ID:ErikGrimes,项目名称:cockroach,代码行数:29,代码来源:range_data_iter.go
示例3: newRangeDataIterator
func newRangeDataIterator(r *Range, e engine.Engine) *rangeDataIterator {
r.RLock()
startKey := r.Desc().StartKey
if startKey.Equal(engine.KeyMin) {
startKey = engine.KeyLocalMax
}
endKey := r.Desc().EndKey
r.RUnlock()
ri := &rangeDataIterator{
ranges: []keyRange{
{
start: engine.MVCCEncodeKey(engine.MakeKey(engine.KeyLocalRangeIDPrefix, encoding.EncodeUvarint(nil, uint64(r.Desc().RaftID)))),
end: engine.MVCCEncodeKey(engine.MakeKey(engine.KeyLocalRangeIDPrefix, encoding.EncodeUvarint(nil, uint64(r.Desc().RaftID+1)))),
},
{
start: engine.MVCCEncodeKey(engine.MakeKey(engine.KeyLocalRangeKeyPrefix, encoding.EncodeBytes(nil, startKey))),
end: engine.MVCCEncodeKey(engine.MakeKey(engine.KeyLocalRangeKeyPrefix, encoding.EncodeBytes(nil, endKey))),
},
{
start: engine.MVCCEncodeKey(startKey),
end: engine.MVCCEncodeKey(endKey),
},
},
iter: e.NewIterator(),
}
ri.iter.Seek(ri.ranges[ri.curIndex].start)
ri.advance()
return ri
}
开发者ID:josephwinston,项目名称:cockroach,代码行数:29,代码来源:range_data_iter.go
示例4: CopyFrom
// CopyFrom copies all the cached results from the originRangeID
// response cache into this one. Note that the cache will not be
// locked while copying is in progress. Failures decoding individual
// cache entries return an error. The copy is done directly using the
// engine instead of interpreting values through MVCC for efficiency.
func (rc *ResponseCache) CopyFrom(e engine.Engine, originRangeID proto.RangeID) error {
prefix := keys.ResponseCacheKey(originRangeID, nil) // response cache prefix
start := engine.MVCCEncodeKey(prefix)
end := engine.MVCCEncodeKey(prefix.PrefixEnd())
return e.Iterate(start, end, func(kv proto.RawKeyValue) (bool, error) {
// Decode the key into a cmd, skipping on error. Otherwise,
// write it to the corresponding key in the new cache.
cmdID, err := rc.decodeResponseCacheKey(kv.Key)
if err != nil {
return false, util.Errorf("could not decode a response cache key %s: %s",
proto.Key(kv.Key), err)
}
key := keys.ResponseCacheKey(rc.rangeID, &cmdID)
encKey := engine.MVCCEncodeKey(key)
// Decode the value, update the checksum and re-encode.
meta := &engine.MVCCMetadata{}
if err := gogoproto.Unmarshal(kv.Value, meta); err != nil {
return false, util.Errorf("could not decode response cache value %s [% x]: %s",
proto.Key(kv.Key), kv.Value, err)
}
meta.Value.Checksum = nil
meta.Value.InitChecksum(key)
_, _, err = engine.PutProto(e, encKey, meta)
return false, err
})
}
开发者ID:mberhault,项目名称:cockroach,代码行数:32,代码来源:response_cache.go
示例5: newReplicaDataIterator
func newReplicaDataIterator(d *roachpb.RangeDescriptor, e engine.Engine) *replicaDataIterator {
// The first range in the keyspace starts at KeyMin, which includes the node-local
// space. We need the original StartKey to find the range metadata, but the
// actual data starts at LocalMax.
dataStartKey := d.StartKey.AsRawKey()
if d.StartKey.Equal(roachpb.RKeyMin) {
dataStartKey = keys.LocalMax
}
ri := &replicaDataIterator{
ranges: []keyRange{
{
start: engine.MVCCEncodeKey(keys.MakeRangeIDPrefix(d.RangeID)),
end: engine.MVCCEncodeKey(keys.MakeRangeIDPrefix(d.RangeID + 1)),
},
{
start: engine.MVCCEncodeKey(keys.MakeRangeKeyPrefix(d.StartKey)),
end: engine.MVCCEncodeKey(keys.MakeRangeKeyPrefix(d.EndKey)),
},
{
start: engine.MVCCEncodeKey(dataStartKey),
end: engine.MVCCEncodeKey(d.EndKey.AsRawKey()),
},
},
iter: e.NewIterator(),
}
ri.iter.Seek(ri.ranges[ri.curIndex].start)
ri.advance()
return ri
}
开发者ID:nporsche,项目名称:cockroach,代码行数:29,代码来源:replica_data_iter.go
示例6: CopyInto
// CopyInto copies all the cached results from this response cache
// into the destRangeID response cache. Failures decoding individual
// cache entries return an error.
func (rc *ResponseCache) CopyInto(e engine.Engine, destRangeID roachpb.RangeID) error {
start := engine.MVCCEncodeKey(
keys.ResponseCacheKey(rc.rangeID, roachpb.KeyMin))
end := engine.MVCCEncodeKey(
keys.ResponseCacheKey(rc.rangeID, roachpb.KeyMax))
return e.Iterate(start, end, func(kv engine.MVCCKeyValue) (bool, error) {
// Decode the key into a cmd, skipping on error. Otherwise,
// write it to the corresponding key in the new cache.
family, err := rc.decodeResponseCacheKey(kv.Key)
if err != nil {
return false, util.Errorf("could not decode a response cache key %s: %s",
roachpb.Key(kv.Key), err)
}
key := keys.ResponseCacheKey(destRangeID, family)
encKey := engine.MVCCEncodeKey(key)
// Decode the value, update the checksum and re-encode.
meta := &engine.MVCCMetadata{}
if err := proto.Unmarshal(kv.Value, meta); err != nil {
return false, util.Errorf("could not decode response cache value %s [% x]: %s",
roachpb.Key(kv.Key), kv.Value, err)
}
meta.Value.Checksum = nil
meta.Value.InitChecksum(key)
_, _, err = engine.PutProto(e, encKey, meta)
return false, err
})
}
开发者ID:xujun10110,项目名称:cockroach,代码行数:31,代码来源:response_cache.go
示例7: InternalTruncateLog
// InternalTruncateLog discards a prefix of the raft log.
func (r *Range) InternalTruncateLog(batch engine.Engine, ms *engine.MVCCStats, args *proto.InternalTruncateLogRequest, reply *proto.InternalTruncateLogResponse) {
// args.Index is the first index to keep.
term, err := r.Term(args.Index - 1)
if err != nil {
reply.SetGoError(err)
return
}
start := keys.RaftLogKey(r.Desc().RaftID, 0)
end := keys.RaftLogKey(r.Desc().RaftID, args.Index)
err = batch.Iterate(engine.MVCCEncodeKey(start), engine.MVCCEncodeKey(end),
func(kv proto.RawKeyValue) (bool, error) {
err := batch.Clear(kv.Key)
return false, err
})
if err != nil {
reply.SetGoError(err)
return
}
ts := proto.RaftTruncatedState{
Index: args.Index - 1,
Term: term,
}
err = engine.MVCCPutProto(batch, ms, keys.RaftTruncatedStateKey(r.Desc().RaftID),
proto.ZeroTimestamp, nil, &ts)
reply.SetGoError(err)
}
开发者ID:simonzhangsm,项目名称:cockroach,代码行数:27,代码来源:range_command.go
示例8: loadRangeDescriptor
func loadRangeDescriptor(
db engine.Engine, rangeID roachpb.RangeID,
) (roachpb.RangeDescriptor, error) {
var desc roachpb.RangeDescriptor
handleKV := func(kv engine.MVCCKeyValue) (bool, error) {
if kv.Key.Timestamp == hlc.ZeroTimestamp {
// We only want values, not MVCCMetadata.
return false, nil
}
if err := checkRangeDescriptorKey(kv.Key); err != nil {
// Range descriptor keys are interleaved with others, so if it
// doesn't parse as a range descriptor just skip it.
return false, nil
}
if err := getProtoValue(kv.Value, &desc); err != nil {
return false, err
}
return desc.RangeID == rangeID, nil
}
// Range descriptors are stored by key, so we have to scan over the
// range-local data to find the one for this RangeID.
start := engine.MakeMVCCMetadataKey(keys.LocalRangePrefix)
end := engine.MakeMVCCMetadataKey(keys.LocalRangeMax)
if err := db.Iterate(start, end, handleKV); err != nil {
return roachpb.RangeDescriptor{}, err
}
if desc.RangeID == rangeID {
return desc, nil
}
return roachpb.RangeDescriptor{}, fmt.Errorf("range descriptor %d not found", rangeID)
}
开发者ID:YuleiXiao,项目名称:cockroach,代码行数:33,代码来源:debug.go
示例9: copySeqCache
func copySeqCache(e engine.Engine, srcID, dstID roachpb.RangeID, keyMin, keyMax engine.MVCCKey) error {
var scratch [64]byte
return e.Iterate(keyMin, keyMax,
func(kv engine.MVCCKeyValue) (bool, error) {
// Decode the key into a cmd, skipping on error. Otherwise,
// write it to the corresponding key in the new cache.
id, epoch, seq, err := decodeSequenceCacheMVCCKey(kv.Key, scratch[:0])
if err != nil {
return false, util.Errorf("could not decode a sequence cache key %s: %s",
kv.Key, err)
}
key := keys.SequenceCacheKey(dstID, id, epoch, seq)
encKey := engine.MakeMVCCMetadataKey(key)
// Decode the value, update the checksum and re-encode.
meta := &engine.MVCCMetadata{}
if err := proto.Unmarshal(kv.Value, meta); err != nil {
return false, util.Errorf("could not decode sequence cache value %s [% x]: %s",
kv.Key, kv.Value, err)
}
value := meta.Value()
value.ClearChecksum()
value.InitChecksum(key)
meta.RawBytes = value.RawBytes
_, _, err = engine.PutProto(e, encKey, meta)
return false, err
})
}
开发者ID:billhongs,项目名称:cockroach,代码行数:27,代码来源:sequence_cache.go
示例10: newReplicaDataIterator
func newReplicaDataIterator(d *roachpb.RangeDescriptor, e engine.Engine) *replicaDataIterator {
ri := &replicaDataIterator{
ranges: makeReplicaKeyRanges(d),
Iterator: e.NewIterator(false),
}
ri.Seek(ri.ranges[ri.curIndex].start)
return ri
}
开发者ID:kaustubhkurve,项目名称:cockroach,代码行数:8,代码来源:replica_data_iter.go
示例11: addStore
// AddStore creates a new store on the same Transport but doesn't create any ranges.
func (m *multiTestContext) addStore() {
idx := len(m.stores)
var clock *hlc.Clock
if len(m.clocks) > idx {
clock = m.clocks[idx]
} else {
clock = m.clock
m.clocks = append(m.clocks, clock)
}
var eng engine.Engine
var needBootstrap bool
if len(m.engines) > idx {
eng = m.engines[idx]
} else {
eng = engine.NewInMem(proto.Attributes{}, 1<<20)
m.engines = append(m.engines, eng)
needBootstrap = true
// Add an extra refcount to the engine so the underlying rocksdb instances
// aren't closed when stopping and restarting the stores.
// These refcounts are removed in Stop().
if err := eng.Open(); err != nil {
m.t.Fatal(err)
}
}
stopper := stop.NewStopper()
ctx := m.makeContext(idx)
store := storage.NewStore(ctx, eng, &proto.NodeDescriptor{NodeID: proto.NodeID(idx + 1)})
if needBootstrap {
err := store.Bootstrap(proto.StoreIdent{
NodeID: proto.NodeID(idx + 1),
StoreID: proto.StoreID(idx + 1),
}, stopper)
if err != nil {
m.t.Fatal(err)
}
// Bootstrap the initial range on the first store
if idx == 0 {
if err := store.BootstrapRange(nil); err != nil {
m.t.Fatal(err)
}
}
}
if err := store.Start(stopper); err != nil {
m.t.Fatal(err)
}
store.WaitForInit()
m.stores = append(m.stores, store)
if len(m.senders) == idx {
m.senders = append(m.senders, kv.NewLocalSender())
}
m.senders[idx].AddStore(store)
// Save the store identities for later so we can use them in
// replication operations even while the store is stopped.
m.idents = append(m.idents, store.Ident)
m.stoppers = append(m.stoppers, stopper)
}
开发者ID:Eric-Gaudiello,项目名称:cockroach,代码行数:59,代码来源:client_test.go
示例12: ClearData
// ClearData removes all persisted items stored in the cache.
func (sc *AbortCache) ClearData(e engine.Engine) error {
b := e.NewBatch()
defer b.Close()
_, err := engine.ClearRange(b, engine.MakeMVCCMetadataKey(sc.min()), engine.MakeMVCCMetadataKey(sc.max()))
if err != nil {
return err
}
return b.Commit()
}
开发者ID:mjibson,项目名称:cockroach,代码行数:10,代码来源:abort_cache.go
示例13: newReplicaDataIterator
func newReplicaDataIterator(d *roachpb.RangeDescriptor, e engine.Engine, replicatedOnly bool) *replicaDataIterator {
rangeFunc := makeAllKeyRanges
if replicatedOnly {
rangeFunc = makeReplicatedKeyRanges
}
ri := &replicaDataIterator{
ranges: rangeFunc(d),
Iterator: e.NewIterator(nil),
}
ri.Seek(ri.ranges[ri.curIndex].start)
return ri
}
开发者ID:petermattis,项目名称:cockroach,代码行数:12,代码来源:replica_data_iter.go
示例14: ComputeStatsForRange
// ComputeStatsForRange computes the stats for a given range by
// iterating over all key ranges for the given range that should
// be accounted for in its stats.
func ComputeStatsForRange(d *roachpb.RangeDescriptor, e engine.Engine, nowNanos int64) (engine.MVCCStats, error) {
iter := e.NewIterator(nil)
defer iter.Close()
ms := engine.MVCCStats{}
for _, r := range makeReplicatedKeyRanges(d) {
msDelta, err := iter.ComputeStats(r.start, r.end, nowNanos)
if err != nil {
return engine.MVCCStats{}, err
}
ms.Add(msDelta)
}
return ms, nil
}
开发者ID:chzyer-dev,项目名称:cockroach,代码行数:17,代码来源:stats.go
示例15: mergeTrigger
// mergeTrigger is called on a successful commit of an AdminMerge
// transaction. It recomputes stats for the receiving range.
func (r *Range) mergeTrigger(batch engine.Engine, merge *proto.MergeTrigger) error {
if !bytes.Equal(r.Desc().StartKey, merge.UpdatedDesc.StartKey) {
return util.Errorf("range and updated range start keys do not match: %s != %s",
r.Desc().StartKey, merge.UpdatedDesc.StartKey)
}
if !r.Desc().EndKey.Less(merge.UpdatedDesc.EndKey) {
return util.Errorf("range end key is not less than the post merge end key: %s >= %s",
r.Desc().EndKey, merge.UpdatedDesc.EndKey)
}
if merge.SubsumedRaftID <= 0 {
return util.Errorf("subsumed raft ID must be provided: %d", merge.SubsumedRaftID)
}
// Copy the subsumed range's response cache to the subsuming one.
if err := r.respCache.CopyFrom(batch, merge.SubsumedRaftID); err != nil {
return util.Errorf("unable to copy response cache to new split range: %s", err)
}
// Compute stats for updated range.
now := r.rm.Clock().Timestamp()
iter := newRangeDataIterator(&merge.UpdatedDesc, batch)
ms, err := engine.MVCCComputeStats(iter, now.WallTime)
iter.Close()
if err != nil {
return util.Errorf("unable to compute stats for the range after merge: %s", err)
}
if err = r.stats.SetMVCCStats(batch, ms); err != nil {
return util.Errorf("unable to write MVCC stats: %s", err)
}
// Clear the timestamp cache. In the case that this replica and the
// subsumed replica each held their respective leader leases, we
// could merge the timestamp caches for efficiency. But it's unlikely
// and not worth the extra logic and potential for error.
r.Lock()
r.tsCache.Clear(r.rm.Clock())
r.Unlock()
batch.Defer(func() {
if err := r.rm.MergeRange(r, merge.UpdatedDesc.EndKey, merge.SubsumedRaftID); err != nil {
// Our in-memory state has diverged from the on-disk state.
log.Fatalf("failed to update store after merging range: %s", err)
}
})
return nil
}
开发者ID:routhcr,项目名称:cockroach,代码行数:50,代码来源:range_command.go
示例16: CopyFrom
// CopyFrom copies all the cached results from another response cache
// into this one. Note that the cache will not be locked while copying
// is in progress. Failures decoding individual cache entries return an
// error. The copy is done directly using the engine instead of interpreting
// values through MVCC for efficiency.
func (rc *ResponseCache) CopyFrom(e engine.Engine, originRaftID int64) error {
prefix := engine.ResponseCacheKey(originRaftID, nil) // response cache prefix
start := engine.MVCCEncodeKey(prefix)
end := engine.MVCCEncodeKey(prefix.PrefixEnd())
return e.Iterate(start, end, func(kv proto.RawKeyValue) (bool, error) {
// Decode the key into a cmd, skipping on error. Otherwise,
// write it to the corresponding key in the new cache.
cmdID, err := rc.decodeResponseCacheKey(kv.Key)
if err != nil {
return false, util.Errorf("could not decode a response cache key %q: %s", kv.Key, err)
}
encKey := engine.MVCCEncodeKey(engine.ResponseCacheKey(rc.raftID, &cmdID))
return false, rc.engine.Put(encKey, kv.Value)
})
}
开发者ID:josephwinston,项目名称:cockroach,代码行数:21,代码来源:response_cache.go
示例17: verifyCleanup
func verifyCleanup(key proto.Key, coord *TxnCoordSender, eng engine.Engine, t *testing.T) {
if len(coord.txns) != 0 {
t.Errorf("expected empty transactions map; got %d", len(coord.txns))
}
if err := util.IsTrueWithin(func() bool {
meta := &engine.MVCCMetadata{}
ok, _, _, err := eng.GetProto(engine.MVCCEncodeKey(key), meta)
if err != nil {
t.Errorf("error getting MVCC metadata: %s", err)
}
return !ok || meta.Txn == nil
}, 500*time.Millisecond); err != nil {
t.Errorf("expected intents to be cleaned up within 500ms")
}
}
开发者ID:backend2use,项目名称:cockroachdb,代码行数:16,代码来源:txn_coord_sender_test.go
示例18: copySeqCache
func copySeqCache(
e engine.Engine,
ms *engine.MVCCStats,
srcID, dstID roachpb.RangeID,
keyMin, keyMax engine.MVCCKey,
) (int, error) {
var scratch [64]byte
var count int
var meta engine.MVCCMetadata
// TODO(spencer): look into making this an MVCCIteration and writing
// the values using MVCC so we can avoid the ugliness of updating
// the MVCCStats by hand below.
err := e.Iterate(keyMin, keyMax,
func(kv engine.MVCCKeyValue) (bool, error) {
// Decode the key, skipping on error. Otherwise, write it to the
// corresponding key in the new cache.
txnID, err := decodeAbortCacheMVCCKey(kv.Key, scratch[:0])
if err != nil {
return false, util.Errorf("could not decode an abort cache key %s: %s", kv.Key, err)
}
key := keys.AbortCacheKey(dstID, txnID)
encKey := engine.MakeMVCCMetadataKey(key)
// Decode the MVCCMetadata value.
if err := proto.Unmarshal(kv.Value, &meta); err != nil {
return false, util.Errorf("could not decode mvcc metadata %s [% x]: %s", kv.Key, kv.Value, err)
}
value := meta.Value()
value.ClearChecksum()
value.InitChecksum(key)
meta.RawBytes = value.RawBytes
keyBytes, valBytes, err := engine.PutProto(e, encKey, &meta)
if err != nil {
return false, err
}
count++
if ms != nil {
ms.SysBytes += keyBytes + valBytes
ms.SysCount++
}
return false, nil
})
return count, err
}
开发者ID:GitGoldie,项目名称:cockroach,代码行数:44,代码来源:abort_cache.go
示例19: verifyCleanup
func verifyCleanup(key proto.Key, coord *TxnCoordSender, eng engine.Engine, t *testing.T) {
util.SucceedsWithin(t, 500*time.Millisecond, func() error {
coord.Lock()
l := len(coord.txns)
coord.Unlock()
if l != 0 {
return fmt.Errorf("expected empty transactions map; got %d", l)
}
meta := &engine.MVCCMetadata{}
ok, _, _, err := eng.GetProto(engine.MVCCEncodeKey(key), meta)
if err != nil {
return fmt.Errorf("error getting MVCC metadata: %s", err)
}
if !ok || meta.Txn == nil {
return nil
}
return errors.New("intents not cleaned up")
})
}
开发者ID:routhcr,项目名称:cockroach,代码行数:19,代码来源:txn_coord_sender_test.go
示例20: verifyCleanup
func verifyCleanup(key roachpb.Key, coord *TxnCoordSender, eng engine.Engine, t *testing.T) {
util.SucceedsWithin(t, 500*time.Millisecond, func() error {
coord.Lock()
l := len(coord.txns)
coord.Unlock()
if l != 0 {
return fmt.Errorf("expected empty transactions map; got %d", l)
}
meta := &engine.MVCCMetadata{}
ok, _, _, err := eng.GetProto(engine.MakeMVCCMetadataKey(key), meta)
if err != nil {
return fmt.Errorf("error getting MVCC metadata: %s", err)
}
if ok && meta.Txn != nil {
return fmt.Errorf("found unexpected write intent: %s", meta)
}
return nil
})
}
开发者ID:kimh,项目名称:cockroach,代码行数:19,代码来源:txn_coord_sender_test.go
注:本文中的github.com/cockroachdb/cockroach/storage/engine.Engine类示例整理自Github/MSDocs等源码及文档管理平台,相关代码片段筛选自各路编程大神贡献的开源项目,源码版权归原作者所有,传播和使用请参考对应项目的License;未经允许,请勿转载。 |
请发表评论