ScutGame edited this page Oct 30, 2015 · 11 revisions

此章节介绍如何设计服务端的实体Entity结构

目的与用途

实体是游戏中基本的存储单元,它可以通过Protobuf序列化的方式存储在Redis库中;还可以通过实体定义的Schema信息生成数据库的表结构,并且保存到数据库中。

实体Schema架构信息

使用如下定制属性提供实体Schema架构信息,如下属性:

  • EntityTable:提供数据库连接串,表名,索引,以及存储方式和Cache结构类型等信息,只能放在类定义上面,EntityTable只能存在一个;
  • EntityField:提供数据库表的字段,主键,字段类型,长度,唯一键和定义对象序列化方式,只能放在成员属性上面,EntityField只能存在一个;
  • EntityFieldExtend:定义实体的字段是可以被子类继承的,只能放在成员属性上面,EntityFieldExtend只能存在一个;

序列化标记属性

  • ProtoContract:Protobuf提供的序列化类配置;
  • ProtoMember:Protobuf提供的序列化成员属性配置,需要提供Tag标签参数,Tag在类与子类中的成员都必须唯一,不能重复,否则序列化会失败;
  • JsonIgnore: Json提供的序列化成员配置,有此标记的成员将不被序列化,其它的成员都可序列化,与Protobuf不同;

EntityTable属性

构造函数参数:

  • connectKey:数据库连接串配置键值,读取Config配置文件中connectionStrings节点的name值;

  • tableName:映射数据库中的表名, 为空时取类名作为表名;

  • periodTime:配置数据加载到缓存中过期时间多久,0表示不过期,单位秒;

  • personalName:指定玩家ID在数据库表中定义的字段名,默认是UserId,不区别大小写,配合定义BaseEntity类型的实体使用;

  • accessLevel:指定实体的访问级别,设置为只读时缓存修改不会自动更新到数据库或Redis中,ReadOnly是给策划配置类型的数值;

  • cacheType:指定使用的缓存结构类型,分为单个实体方式存储,字典方式存储(使用GetIdentity方法作为字典的Key),队列方式存储(在聊天模型中使用);

  • isStoreInDb:这个在6.7.9.6以上已经不在使用;

扩展成员属性:

使用"成员属性名="设置值

  • IncreaseStartNo:设置自增方式的主键的起始编号,此编号不同于数据库中的自增编号;

  • IsExpired:设置实体是否会过期的,配合periodTime使用;

  • StorageType:设置缓存的数据从Redis或数据库加载方式,以及是否可以同步到Redis或数据库,使用 '|'或运算符号可以配置多个;

  • Capacity:指定从数据库加载数据的数量,同数据库中的Top语句;

  • Condition:指定从数据库加载数据的过滤条件,同数据库中的where语句;

  • OrderColumn:指定从数据库加载数据的排序方式,同数据库中的orderby语句;

  • TableNameFormat:自定表生成规则,在游戏中针对游戏日志以年月方式归档存储使用;

  • Indexs: 提供创建数据库表的索引配置,允许创建多组索引,其中字符串可以使用','来设置复合索引字段;

  • 示例

(1) 创建一个UserId的索引和以多列"Col1,Col2"组合的索引;

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,Indexs=new[]{"UserID","Col1,Col2"})]publicclassMyEntityLog:LogEntity{[EntityField(true,IsIdentity=true)]publiclongLogId{get;set;}[EntityField]publicintUserID{get;set;}[EntityField]publicintCol1{get;set;}[EntityField]publicintCol2{get;set;}}

(2) 创建日志表,"Log_"是固定字符,“$date[yyyyMMdd]"是日期表达式,"{0}"是Model的名称,如果一年中的第几周表示"$week",如:"Log_$date[yyyyMM]$week_{0}";

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,TableNameFormat="Log_$date[yyyyMMdd]_{0}")]publicclassMyEntityLog:LogEntity{}

(3) 从数据库查找UserId为1380000,取前100条,并以ID降序

[EntityTable(AccessLevel.ReadWrite,DbCofingKeys.DataKey,Capacity=100,Condition="UserId=1380000",OrderColumn="ID DESC",StorageType=StorageType.ReadWriteDB ")]publicclassMyEntityData:BaseEntity{[EntityField(true)]publicintID{get;set;}[EntityField(true)]publicintUserId{get;set;}}

EntityField属性

构造函数参数:

  • fieldName:设置数据库表中的字段名,为空时取属性的名称;

  • isKey:设置数据库表中的主键,当多个属性都设置为True时,表示主键是复合主键;

  • isJsonSerialize:设置属性是Ojbect类型的字段且可Json序列化的;

  • dbType:设置属性在数据库表中的字段类型,如果未设置则根据属性的类型自动匹配;

扩展成员属性:

使用"成员属性名="设置值

  • ColumnLength:设置数据库表中字段的长度,varchar默认值为255;

  • ColumnScale:设置数据库表中字段为符点数时保留的小数位数;

  • Isnullable:设置数据库表中字段是否可为空值;

  • IsUnique:设置数据库表中字段的唯一约束,多个属性都配置时表示复合唯一约束,暂不支持多组唯一约束配置;

  • IsIdentity:设置数据库表中字段是可自增的字段, 设置为true后,此对象将不能Update的方式同步到数据了,可以Insert和Delete

  • IdentityNo:设置数据库表中可自增的字段的起始值,与IsIdentity一起使用;

  • Disable:设置字段不从数据库中取值;

  • JsonDateTimeFormat:设置字段为Object类型时,Json序列化的日期格式,与isJsonSerialize配合使用;

EntityFieldExtend属性

设置类的属性可以在子类中使用,是可继承的。

  • 示例

定义Col1与Col2属性是可以被子类继承的;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntity:BaseEntity{[EntityField]publicintUserID{get;set;}[EntityField][EntityFieldExtend]publicintCol1{get;set;}[EntityField][EntityFieldExtend]publicintCol2{get;set;}}

在MyEntityExtend类中包括父亲的Col1与Col2属性;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntityExtend:MyEntity{[EntityField]publicintUserID{get;set;}[EntityField]publicintCol3{get;set;}}

实体定义

实体的结构

结构包括:私有(BaseEntity)、共享(ShareEntity)、排名(RankEntity)、日志(LogEntity)类型;其中共享以分为只读和读写类型;共享只读类型主要是定义策划的数值配置。

私有(BaseEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Dictionary,与PersonalCacheStruct缓存一起使用,成员中必须包含以UserId为名称的主键字段;

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassMyEntity:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

共享(ShareEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Entity,与PersonalCacheStruct缓存一起使用,成员中必须包含一个主键属性;

[ProtoContract][EntityTable(CacheType.Entity,"{数据库连接配置键}"})]publicclassEntityData:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

只读类型

需要配置EntityTable的AccessLevel为ReadOnly,此实体不能写,只从数据库中读取,可以不定义序列化标记,定义也不会有错;

[ProtoContract][EntityTable(AccessLevel.ReadOnly,"{数据库连接配置键}"})]publicclassEntityInfo:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

排名(RankEntity)

此实体子类是针对有序类型的数据,需要配置EntityTable的CacheType属性为Rank,与RankCacheStruct缓存一起使用;

示例: 聊天功能,聊天数据以MsgType分类在Redis中存储

publicenumMsgType{/// <summary>/// 1:世界/// </summary>Word=1,/// <summary>/// 2:联盟/// </summary>Union,/// <summary>/// 3:私聊/// </summary>Whisper,/// <summary>/// 4:其它/// </summary>Other}[ProtoContract][EntityTable(CacheType.Rank,"",StorageType=StorageType.ReadWriteRedis)]publicclassChatInfo:RankEntity{publicoverridestringKey{get{returnMsgType.ToString();}}[ProtoMember(1)]publicMsgTypeMsgType{get;set;}[ProtoMember(2)]publicstringMessage{get;set;}[ProtoMember(3)]publicDateTimeSendTime{get;set;}}

使用RankCacheStruct操作RankEntity实体,如:

varcache=newRankCacheStruct<ChatInfo>();varchatInfo=newChatInfo(){MsgType=MsgType.Word,Message="Hello",Score=DateTime.Now.Ticks};cache.AddRank(chatData.Key,chatInfo);//根据Score字段插入排序//取前50条记录List<ChatInfo>items;if(cache.TryTakeRank(msgType,50,outitems)){}

还有ExchangeRank方法将名次1与2的两个对象对调。

日志(LogEntity)

日志类型的实体是不需要在cache中存储的,需要配制EntityTable的AccessLevel属性为只写的,直接使用DataSyncQueueManager类更新到数据库或redis库,成员中必须包含一个主键属性,可以设置数据库自增;

在数据库中表是以年月的方式存储,当数据增长过大需要清理数据库时,可以将旧月份的表直接删除再收缩数据库,默认预先创建3个月的表;

[EntityTable(AccessLevel.WriteOnly,"{数据库连接配置键}"})]publicclassMyLog:LogEntity{[EntityField(true,IsIdentity=true,IdentityNo=100)]publiclongLogId{get;set;}}

实体的Object类型成员

定义实体的成员属性可以包括:基础类型(byte、bool、short、int、long、float、double、decimal、string和枚举等),以及EntityChangeEvent子类对象,CacheList列表和CacheDictionary字典等类型;

基础类型的成员属性的定义就不详细介绍了,这里主要介绍下如何定义自定的对象类型成员;

EntityChangeEvent子类对象

它作为Entity成员属性中的自定定义类型,Json序列化存储在表的字段中,继承它的子类对象将具有修改通知事件,修改后的属性会同步到Redis或数据库中。

定义背包项BagItem子类

[ProtoContract]publicclassBagItem:EntityChangeEvent{[ProtoMember(1)]publicintId{get;set;}[ProtoMember(2)]publicintNum{get;set;}}

定义玩家UserBag背包实体,自定义成员属性需要配置isJsonSerialize为true,且数据库列的类型为Text(在mysql中text与longtext有区分,mssql不区分),

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicBagItemItem{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheList列表对象

同以上背包示例,修改成列表结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheDictionary字典对象

同以上背包示例,修改成字典结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheDictionary<int,BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

开发小提示

  • 在Enitty属性被修改时,需要额外做些事情,那么开发者要如何处理呢?

开发者首先都会想到直接在Enitty的属性里修改,为了保持Entity的干净,尽量不增加的其它方法,可以在CsScript目录下增加一层实体操作层(Controller),负责对实体的增册改查操作;

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
ScutGame edited this page Oct 30, 2015 · 11 revisions

此章节介绍如何设计服务端的实体Entity结构

目的与用途

实体是游戏中基本的存储单元,它可以通过Protobuf序列化的方式存储在Redis库中;还可以通过实体定义的Schema信息生成数据库的表结构,并且保存到数据库中。

实体Schema架构信息

使用如下定制属性提供实体Schema架构信息,如下属性:

  • EntityTable:提供数据库连接串,表名,索引,以及存储方式和Cache结构类型等信息,只能放在类定义上面,EntityTable只能存在一个;
  • EntityField:提供数据库表的字段,主键,字段类型,长度,唯一键和定义对象序列化方式,只能放在成员属性上面,EntityField只能存在一个;
  • EntityFieldExtend:定义实体的字段是可以被子类继承的,只能放在成员属性上面,EntityFieldExtend只能存在一个;

序列化标记属性

  • ProtoContract:Protobuf提供的序列化类配置;
  • ProtoMember:Protobuf提供的序列化成员属性配置,需要提供Tag标签参数,Tag在类与子类中的成员都必须唯一,不能重复,否则序列化会失败;
  • JsonIgnore: Json提供的序列化成员配置,有此标记的成员将不被序列化,其它的成员都可序列化,与Protobuf不同;

EntityTable属性

构造函数参数:

  • connectKey:数据库连接串配置键值,读取Config配置文件中connectionStrings节点的name值;

  • tableName:映射数据库中的表名, 为空时取类名作为表名;

  • periodTime:配置数据加载到缓存中过期时间多久,0表示不过期,单位秒;

  • personalName:指定玩家ID在数据库表中定义的字段名,默认是UserId,不区别大小写,配合定义BaseEntity类型的实体使用;

  • accessLevel:指定实体的访问级别,设置为只读时缓存修改不会自动更新到数据库或Redis中,ReadOnly是给策划配置类型的数值;

  • cacheType:指定使用的缓存结构类型,分为单个实体方式存储,字典方式存储(使用GetIdentity方法作为字典的Key),队列方式存储(在聊天模型中使用);

  • isStoreInDb:这个在6.7.9.6以上已经不在使用;

扩展成员属性:

使用"成员属性名="设置值

  • IncreaseStartNo:设置自增方式的主键的起始编号,此编号不同于数据库中的自增编号;

  • IsExpired:设置实体是否会过期的,配合periodTime使用;

  • StorageType:设置缓存的数据从Redis或数据库加载方式,以及是否可以同步到Redis或数据库,使用 '|'或运算符号可以配置多个;

  • Capacity:指定从数据库加载数据的数量,同数据库中的Top语句;

  • Condition:指定从数据库加载数据的过滤条件,同数据库中的where语句;

  • OrderColumn:指定从数据库加载数据的排序方式,同数据库中的orderby语句;

  • TableNameFormat:自定表生成规则,在游戏中针对游戏日志以年月方式归档存储使用;

  • Indexs: 提供创建数据库表的索引配置,允许创建多组索引,其中字符串可以使用','来设置复合索引字段;

  • 示例

(1) 创建一个UserId的索引和以多列"Col1,Col2"组合的索引;

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,Indexs=new[]{"UserID","Col1,Col2"})]publicclassMyEntityLog:LogEntity{[EntityField(true,IsIdentity=true)]publiclongLogId{get;set;}[EntityField]publicintUserID{get;set;}[EntityField]publicintCol1{get;set;}[EntityField]publicintCol2{get;set;}}

(2) 创建日志表,"Log_"是固定字符,“$date[yyyyMMdd]"是日期表达式,"{0}"是Model的名称,如果一年中的第几周表示"$week",如:"Log_$date[yyyyMM]$week_{0}";

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,TableNameFormat="Log_$date[yyyyMMdd]_{0}")]publicclassMyEntityLog:LogEntity{}

(3) 从数据库查找UserId为1380000,取前100条,并以ID降序

[EntityTable(AccessLevel.ReadWrite,DbCofingKeys.DataKey,Capacity=100,Condition="UserId=1380000",OrderColumn="ID DESC",StorageType=StorageType.ReadWriteDB ")]publicclassMyEntityData:BaseEntity{[EntityField(true)]publicintID{get;set;}[EntityField(true)]publicintUserId{get;set;}}

EntityField属性

构造函数参数:

  • fieldName:设置数据库表中的字段名,为空时取属性的名称;

  • isKey:设置数据库表中的主键,当多个属性都设置为True时,表示主键是复合主键;

  • isJsonSerialize:设置属性是Ojbect类型的字段且可Json序列化的;

  • dbType:设置属性在数据库表中的字段类型,如果未设置则根据属性的类型自动匹配;

扩展成员属性:

使用"成员属性名="设置值

  • ColumnLength:设置数据库表中字段的长度,varchar默认值为255;

  • ColumnScale:设置数据库表中字段为符点数时保留的小数位数;

  • Isnullable:设置数据库表中字段是否可为空值;

  • IsUnique:设置数据库表中字段的唯一约束,多个属性都配置时表示复合唯一约束,暂不支持多组唯一约束配置;

  • IsIdentity:设置数据库表中字段是可自增的字段, 设置为true后,此对象将不能Update的方式同步到数据了,可以Insert和Delete

  • IdentityNo:设置数据库表中可自增的字段的起始值,与IsIdentity一起使用;

  • Disable:设置字段不从数据库中取值;

  • JsonDateTimeFormat:设置字段为Object类型时,Json序列化的日期格式,与isJsonSerialize配合使用;

EntityFieldExtend属性

设置类的属性可以在子类中使用,是可继承的。

  • 示例

定义Col1与Col2属性是可以被子类继承的;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntity:BaseEntity{[EntityField]publicintUserID{get;set;}[EntityField][EntityFieldExtend]publicintCol1{get;set;}[EntityField][EntityFieldExtend]publicintCol2{get;set;}}

在MyEntityExtend类中包括父亲的Col1与Col2属性;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntityExtend:MyEntity{[EntityField]publicintUserID{get;set;}[EntityField]publicintCol3{get;set;}}

实体定义

实体的结构

结构包括:私有(BaseEntity)、共享(ShareEntity)、排名(RankEntity)、日志(LogEntity)类型;其中共享以分为只读和读写类型;共享只读类型主要是定义策划的数值配置。

私有(BaseEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Dictionary,与PersonalCacheStruct缓存一起使用,成员中必须包含以UserId为名称的主键字段;

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassMyEntity:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

共享(ShareEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Entity,与PersonalCacheStruct缓存一起使用,成员中必须包含一个主键属性;

[ProtoContract][EntityTable(CacheType.Entity,"{数据库连接配置键}"})]publicclassEntityData:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

只读类型

需要配置EntityTable的AccessLevel为ReadOnly,此实体不能写,只从数据库中读取,可以不定义序列化标记,定义也不会有错;

[ProtoContract][EntityTable(AccessLevel.ReadOnly,"{数据库连接配置键}"})]publicclassEntityInfo:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

排名(RankEntity)

此实体子类是针对有序类型的数据,需要配置EntityTable的CacheType属性为Rank,与RankCacheStruct缓存一起使用;

示例: 聊天功能,聊天数据以MsgType分类在Redis中存储

publicenumMsgType{/// <summary>/// 1:世界/// </summary>Word=1,/// <summary>/// 2:联盟/// </summary>Union,/// <summary>/// 3:私聊/// </summary>Whisper,/// <summary>/// 4:其它/// </summary>Other}[ProtoContract][EntityTable(CacheType.Rank,"",StorageType=StorageType.ReadWriteRedis)]publicclassChatInfo:RankEntity{publicoverridestringKey{get{returnMsgType.ToString();}}[ProtoMember(1)]publicMsgTypeMsgType{get;set;}[ProtoMember(2)]publicstringMessage{get;set;}[ProtoMember(3)]publicDateTimeSendTime{get;set;}}

使用RankCacheStruct操作RankEntity实体,如:

varcache=newRankCacheStruct<ChatInfo>();varchatInfo=newChatInfo(){MsgType=MsgType.Word,Message="Hello",Score=DateTime.Now.Ticks};cache.AddRank(chatData.Key,chatInfo);//根据Score字段插入排序//取前50条记录List<ChatInfo>items;if(cache.TryTakeRank(msgType,50,outitems)){}

还有ExchangeRank方法将名次1与2的两个对象对调。

日志(LogEntity)

日志类型的实体是不需要在cache中存储的,需要配制EntityTable的AccessLevel属性为只写的,直接使用DataSyncQueueManager类更新到数据库或redis库,成员中必须包含一个主键属性,可以设置数据库自增;

在数据库中表是以年月的方式存储,当数据增长过大需要清理数据库时,可以将旧月份的表直接删除再收缩数据库,默认预先创建3个月的表;

[EntityTable(AccessLevel.WriteOnly,"{数据库连接配置键}"})]publicclassMyLog:LogEntity{[EntityField(true,IsIdentity=true,IdentityNo=100)]publiclongLogId{get;set;}}

实体的Object类型成员

定义实体的成员属性可以包括:基础类型(byte、bool、short、int、long、float、double、decimal、string和枚举等),以及EntityChangeEvent子类对象,CacheList列表和CacheDictionary字典等类型;

基础类型的成员属性的定义就不详细介绍了,这里主要介绍下如何定义自定的对象类型成员;

EntityChangeEvent子类对象

它作为Entity成员属性中的自定定义类型,Json序列化存储在表的字段中,继承它的子类对象将具有修改通知事件,修改后的属性会同步到Redis或数据库中。

定义背包项BagItem子类

[ProtoContract]publicclassBagItem:EntityChangeEvent{[ProtoMember(1)]publicintId{get;set;}[ProtoMember(2)]publicintNum{get;set;}}

定义玩家UserBag背包实体,自定义成员属性需要配置isJsonSerialize为true,且数据库列的类型为Text(在mysql中text与longtext有区分,mssql不区分),

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicBagItemItem{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheList列表对象

同以上背包示例,修改成列表结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheDictionary字典对象

同以上背包示例,修改成字典结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheDictionary<int,BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

开发小提示

  • 在Enitty属性被修改时,需要额外做些事情,那么开发者要如何处理呢?

开发者首先都会想到直接在Enitty的属性里修改,为了保持Entity的干净,尽量不增加的其它方法,可以在CsScript目录下增加一层实体操作层(Controller),负责对实体的增册改查操作;

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
ScutGame edited this page Oct 30, 2015 · 11 revisions

此章节介绍如何设计服务端的实体Entity结构

目的与用途

实体是游戏中基本的存储单元,它可以通过Protobuf序列化的方式存储在Redis库中;还可以通过实体定义的Schema信息生成数据库的表结构,并且保存到数据库中。

实体Schema架构信息

使用如下定制属性提供实体Schema架构信息,如下属性:

  • EntityTable:提供数据库连接串,表名,索引,以及存储方式和Cache结构类型等信息,只能放在类定义上面,EntityTable只能存在一个;
  • EntityField:提供数据库表的字段,主键,字段类型,长度,唯一键和定义对象序列化方式,只能放在成员属性上面,EntityField只能存在一个;
  • EntityFieldExtend:定义实体的字段是可以被子类继承的,只能放在成员属性上面,EntityFieldExtend只能存在一个;

序列化标记属性

  • ProtoContract:Protobuf提供的序列化类配置;
  • ProtoMember:Protobuf提供的序列化成员属性配置,需要提供Tag标签参数,Tag在类与子类中的成员都必须唯一,不能重复,否则序列化会失败;
  • JsonIgnore: Json提供的序列化成员配置,有此标记的成员将不被序列化,其它的成员都可序列化,与Protobuf不同;

EntityTable属性

构造函数参数:

  • connectKey:数据库连接串配置键值,读取Config配置文件中connectionStrings节点的name值;

  • tableName:映射数据库中的表名, 为空时取类名作为表名;

  • periodTime:配置数据加载到缓存中过期时间多久,0表示不过期,单位秒;

  • personalName:指定玩家ID在数据库表中定义的字段名,默认是UserId,不区别大小写,配合定义BaseEntity类型的实体使用;

  • accessLevel:指定实体的访问级别,设置为只读时缓存修改不会自动更新到数据库或Redis中,ReadOnly是给策划配置类型的数值;

  • cacheType:指定使用的缓存结构类型,分为单个实体方式存储,字典方式存储(使用GetIdentity方法作为字典的Key),队列方式存储(在聊天模型中使用);

  • isStoreInDb:这个在6.7.9.6以上已经不在使用;

扩展成员属性:

使用"成员属性名="设置值

  • IncreaseStartNo:设置自增方式的主键的起始编号,此编号不同于数据库中的自增编号;

  • IsExpired:设置实体是否会过期的,配合periodTime使用;

  • StorageType:设置缓存的数据从Redis或数据库加载方式,以及是否可以同步到Redis或数据库,使用 '|'或运算符号可以配置多个;

  • Capacity:指定从数据库加载数据的数量,同数据库中的Top语句;

  • Condition:指定从数据库加载数据的过滤条件,同数据库中的where语句;

  • OrderColumn:指定从数据库加载数据的排序方式,同数据库中的orderby语句;

  • TableNameFormat:自定表生成规则,在游戏中针对游戏日志以年月方式归档存储使用;

  • Indexs: 提供创建数据库表的索引配置,允许创建多组索引,其中字符串可以使用','来设置复合索引字段;

  • 示例

(1) 创建一个UserId的索引和以多列"Col1,Col2"组合的索引;

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,Indexs=new[]{"UserID","Col1,Col2"})]publicclassMyEntityLog:LogEntity{[EntityField(true,IsIdentity=true)]publiclongLogId{get;set;}[EntityField]publicintUserID{get;set;}[EntityField]publicintCol1{get;set;}[EntityField]publicintCol2{get;set;}}

(2) 创建日志表,"Log_"是固定字符,“$date[yyyyMMdd]"是日期表达式,"{0}"是Model的名称,如果一年中的第几周表示"$week",如:"Log_$date[yyyyMM]$week_{0}";

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,TableNameFormat="Log_$date[yyyyMMdd]_{0}")]publicclassMyEntityLog:LogEntity{}

(3) 从数据库查找UserId为1380000,取前100条,并以ID降序

[EntityTable(AccessLevel.ReadWrite,DbCofingKeys.DataKey,Capacity=100,Condition="UserId=1380000",OrderColumn="ID DESC",StorageType=StorageType.ReadWriteDB ")]publicclassMyEntityData:BaseEntity{[EntityField(true)]publicintID{get;set;}[EntityField(true)]publicintUserId{get;set;}}

EntityField属性

构造函数参数:

  • fieldName:设置数据库表中的字段名,为空时取属性的名称;

  • isKey:设置数据库表中的主键,当多个属性都设置为True时,表示主键是复合主键;

  • isJsonSerialize:设置属性是Ojbect类型的字段且可Json序列化的;

  • dbType:设置属性在数据库表中的字段类型,如果未设置则根据属性的类型自动匹配;

扩展成员属性:

使用"成员属性名="设置值

  • ColumnLength:设置数据库表中字段的长度,varchar默认值为255;

  • ColumnScale:设置数据库表中字段为符点数时保留的小数位数;

  • Isnullable:设置数据库表中字段是否可为空值;

  • IsUnique:设置数据库表中字段的唯一约束,多个属性都配置时表示复合唯一约束,暂不支持多组唯一约束配置;

  • IsIdentity:设置数据库表中字段是可自增的字段, 设置为true后,此对象将不能Update的方式同步到数据了,可以Insert和Delete

  • IdentityNo:设置数据库表中可自增的字段的起始值,与IsIdentity一起使用;

  • Disable:设置字段不从数据库中取值;

  • JsonDateTimeFormat:设置字段为Object类型时,Json序列化的日期格式,与isJsonSerialize配合使用;

EntityFieldExtend属性

设置类的属性可以在子类中使用,是可继承的。

  • 示例

定义Col1与Col2属性是可以被子类继承的;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntity:BaseEntity{[EntityField]publicintUserID{get;set;}[EntityField][EntityFieldExtend]publicintCol1{get;set;}[EntityField][EntityFieldExtend]publicintCol2{get;set;}}

在MyEntityExtend类中包括父亲的Col1与Col2属性;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntityExtend:MyEntity{[EntityField]publicintUserID{get;set;}[EntityField]publicintCol3{get;set;}}

实体定义

实体的结构

结构包括:私有(BaseEntity)、共享(ShareEntity)、排名(RankEntity)、日志(LogEntity)类型;其中共享以分为只读和读写类型;共享只读类型主要是定义策划的数值配置。

私有(BaseEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Dictionary,与PersonalCacheStruct缓存一起使用,成员中必须包含以UserId为名称的主键字段;

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassMyEntity:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

共享(ShareEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Entity,与PersonalCacheStruct缓存一起使用,成员中必须包含一个主键属性;

[ProtoContract][EntityTable(CacheType.Entity,"{数据库连接配置键}"})]publicclassEntityData:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

只读类型

需要配置EntityTable的AccessLevel为ReadOnly,此实体不能写,只从数据库中读取,可以不定义序列化标记,定义也不会有错;

[ProtoContract][EntityTable(AccessLevel.ReadOnly,"{数据库连接配置键}"})]publicclassEntityInfo:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

排名(RankEntity)

此实体子类是针对有序类型的数据,需要配置EntityTable的CacheType属性为Rank,与RankCacheStruct缓存一起使用;

示例: 聊天功能,聊天数据以MsgType分类在Redis中存储

publicenumMsgType{/// <summary>/// 1:世界/// </summary>Word=1,/// <summary>/// 2:联盟/// </summary>Union,/// <summary>/// 3:私聊/// </summary>Whisper,/// <summary>/// 4:其它/// </summary>Other}[ProtoContract][EntityTable(CacheType.Rank,"",StorageType=StorageType.ReadWriteRedis)]publicclassChatInfo:RankEntity{publicoverridestringKey{get{returnMsgType.ToString();}}[ProtoMember(1)]publicMsgTypeMsgType{get;set;}[ProtoMember(2)]publicstringMessage{get;set;}[ProtoMember(3)]publicDateTimeSendTime{get;set;}}

使用RankCacheStruct操作RankEntity实体,如:

varcache=newRankCacheStruct<ChatInfo>();varchatInfo=newChatInfo(){MsgType=MsgType.Word,Message="Hello",Score=DateTime.Now.Ticks};cache.AddRank(chatData.Key,chatInfo);//根据Score字段插入排序//取前50条记录List<ChatInfo>items;if(cache.TryTakeRank(msgType,50,outitems)){}

还有ExchangeRank方法将名次1与2的两个对象对调。

日志(LogEntity)

日志类型的实体是不需要在cache中存储的,需要配制EntityTable的AccessLevel属性为只写的,直接使用DataSyncQueueManager类更新到数据库或redis库,成员中必须包含一个主键属性,可以设置数据库自增;

在数据库中表是以年月的方式存储,当数据增长过大需要清理数据库时,可以将旧月份的表直接删除再收缩数据库,默认预先创建3个月的表;

[EntityTable(AccessLevel.WriteOnly,"{数据库连接配置键}"})]publicclassMyLog:LogEntity{[EntityField(true,IsIdentity=true,IdentityNo=100)]publiclongLogId{get;set;}}

实体的Object类型成员

定义实体的成员属性可以包括:基础类型(byte、bool、short、int、long、float、double、decimal、string和枚举等),以及EntityChangeEvent子类对象,CacheList列表和CacheDictionary字典等类型;

基础类型的成员属性的定义就不详细介绍了,这里主要介绍下如何定义自定的对象类型成员;

EntityChangeEvent子类对象

它作为Entity成员属性中的自定定义类型,Json序列化存储在表的字段中,继承它的子类对象将具有修改通知事件,修改后的属性会同步到Redis或数据库中。

定义背包项BagItem子类

[ProtoContract]publicclassBagItem:EntityChangeEvent{[ProtoMember(1)]publicintId{get;set;}[ProtoMember(2)]publicintNum{get;set;}}

定义玩家UserBag背包实体,自定义成员属性需要配置isJsonSerialize为true,且数据库列的类型为Text(在mysql中text与longtext有区分,mssql不区分),

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicBagItemItem{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheList列表对象

同以上背包示例,修改成列表结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheDictionary字典对象

同以上背包示例,修改成字典结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheDictionary<int,BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

开发小提示

  • 在Enitty属性被修改时,需要额外做些事情,那么开发者要如何处理呢?

开发者首先都会想到直接在Enitty的属性里修改,为了保持Entity的干净,尽量不增加的其它方法,可以在CsScript目录下增加一层实体操作层(Controller),负责对实体的增册改查操作;

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
ScutGame edited this page Oct 30, 2015 · 11 revisions

此章节介绍如何设计服务端的实体Entity结构

目的与用途

实体是游戏中基本的存储单元,它可以通过Protobuf序列化的方式存储在Redis库中;还可以通过实体定义的Schema信息生成数据库的表结构,并且保存到数据库中。

实体Schema架构信息

使用如下定制属性提供实体Schema架构信息,如下属性:

  • EntityTable:提供数据库连接串,表名,索引,以及存储方式和Cache结构类型等信息,只能放在类定义上面,EntityTable只能存在一个;
  • EntityField:提供数据库表的字段,主键,字段类型,长度,唯一键和定义对象序列化方式,只能放在成员属性上面,EntityField只能存在一个;
  • EntityFieldExtend:定义实体的字段是可以被子类继承的,只能放在成员属性上面,EntityFieldExtend只能存在一个;

序列化标记属性

  • ProtoContract:Protobuf提供的序列化类配置;
  • ProtoMember:Protobuf提供的序列化成员属性配置,需要提供Tag标签参数,Tag在类与子类中的成员都必须唯一,不能重复,否则序列化会失败;
  • JsonIgnore: Json提供的序列化成员配置,有此标记的成员将不被序列化,其它的成员都可序列化,与Protobuf不同;

EntityTable属性

构造函数参数:

  • connectKey:数据库连接串配置键值,读取Config配置文件中connectionStrings节点的name值;

  • tableName:映射数据库中的表名, 为空时取类名作为表名;

  • periodTime:配置数据加载到缓存中过期时间多久,0表示不过期,单位秒;

  • personalName:指定玩家ID在数据库表中定义的字段名,默认是UserId,不区别大小写,配合定义BaseEntity类型的实体使用;

  • accessLevel:指定实体的访问级别,设置为只读时缓存修改不会自动更新到数据库或Redis中,ReadOnly是给策划配置类型的数值;

  • cacheType:指定使用的缓存结构类型,分为单个实体方式存储,字典方式存储(使用GetIdentity方法作为字典的Key),队列方式存储(在聊天模型中使用);

  • isStoreInDb:这个在6.7.9.6以上已经不在使用;

扩展成员属性:

使用"成员属性名="设置值

  • IncreaseStartNo:设置自增方式的主键的起始编号,此编号不同于数据库中的自增编号;

  • IsExpired:设置实体是否会过期的,配合periodTime使用;

  • StorageType:设置缓存的数据从Redis或数据库加载方式,以及是否可以同步到Redis或数据库,使用 '|'或运算符号可以配置多个;

  • Capacity:指定从数据库加载数据的数量,同数据库中的Top语句;

  • Condition:指定从数据库加载数据的过滤条件,同数据库中的where语句;

  • OrderColumn:指定从数据库加载数据的排序方式,同数据库中的orderby语句;

  • TableNameFormat:自定表生成规则,在游戏中针对游戏日志以年月方式归档存储使用;

  • Indexs: 提供创建数据库表的索引配置,允许创建多组索引,其中字符串可以使用','来设置复合索引字段;

  • 示例

(1) 创建一个UserId的索引和以多列"Col1,Col2"组合的索引;

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,Indexs=new[]{"UserID","Col1,Col2"})]publicclassMyEntityLog:LogEntity{[EntityField(true,IsIdentity=true)]publiclongLogId{get;set;}[EntityField]publicintUserID{get;set;}[EntityField]publicintCol1{get;set;}[EntityField]publicintCol2{get;set;}}

(2) 创建日志表,"Log_"是固定字符,“$date[yyyyMMdd]"是日期表达式,"{0}"是Model的名称,如果一年中的第几周表示"$week",如:"Log_$date[yyyyMM]$week_{0}";

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,TableNameFormat="Log_$date[yyyyMMdd]_{0}")]publicclassMyEntityLog:LogEntity{}

(3) 从数据库查找UserId为1380000,取前100条,并以ID降序

[EntityTable(AccessLevel.ReadWrite,DbCofingKeys.DataKey,Capacity=100,Condition="UserId=1380000",OrderColumn="ID DESC",StorageType=StorageType.ReadWriteDB ")]publicclassMyEntityData:BaseEntity{[EntityField(true)]publicintID{get;set;}[EntityField(true)]publicintUserId{get;set;}}

EntityField属性

构造函数参数:

  • fieldName:设置数据库表中的字段名,为空时取属性的名称;

  • isKey:设置数据库表中的主键,当多个属性都设置为True时,表示主键是复合主键;

  • isJsonSerialize:设置属性是Ojbect类型的字段且可Json序列化的;

  • dbType:设置属性在数据库表中的字段类型,如果未设置则根据属性的类型自动匹配;

扩展成员属性:

使用"成员属性名="设置值

  • ColumnLength:设置数据库表中字段的长度,varchar默认值为255;

  • ColumnScale:设置数据库表中字段为符点数时保留的小数位数;

  • Isnullable:设置数据库表中字段是否可为空值;

  • IsUnique:设置数据库表中字段的唯一约束,多个属性都配置时表示复合唯一约束,暂不支持多组唯一约束配置;

  • IsIdentity:设置数据库表中字段是可自增的字段, 设置为true后,此对象将不能Update的方式同步到数据了,可以Insert和Delete

  • IdentityNo:设置数据库表中可自增的字段的起始值,与IsIdentity一起使用;

  • Disable:设置字段不从数据库中取值;

  • JsonDateTimeFormat:设置字段为Object类型时,Json序列化的日期格式,与isJsonSerialize配合使用;

EntityFieldExtend属性

设置类的属性可以在子类中使用,是可继承的。

  • 示例

定义Col1与Col2属性是可以被子类继承的;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntity:BaseEntity{[EntityField]publicintUserID{get;set;}[EntityField][EntityFieldExtend]publicintCol1{get;set;}[EntityField][EntityFieldExtend]publicintCol2{get;set;}}

在MyEntityExtend类中包括父亲的Col1与Col2属性;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntityExtend:MyEntity{[EntityField]publicintUserID{get;set;}[EntityField]publicintCol3{get;set;}}

实体定义

实体的结构

结构包括:私有(BaseEntity)、共享(ShareEntity)、排名(RankEntity)、日志(LogEntity)类型;其中共享以分为只读和读写类型;共享只读类型主要是定义策划的数值配置。

私有(BaseEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Dictionary,与PersonalCacheStruct缓存一起使用,成员中必须包含以UserId为名称的主键字段;

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassMyEntity:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

共享(ShareEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Entity,与PersonalCacheStruct缓存一起使用,成员中必须包含一个主键属性;

[ProtoContract][EntityTable(CacheType.Entity,"{数据库连接配置键}"})]publicclassEntityData:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

只读类型

需要配置EntityTable的AccessLevel为ReadOnly,此实体不能写,只从数据库中读取,可以不定义序列化标记,定义也不会有错;

[ProtoContract][EntityTable(AccessLevel.ReadOnly,"{数据库连接配置键}"})]publicclassEntityInfo:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

排名(RankEntity)

此实体子类是针对有序类型的数据,需要配置EntityTable的CacheType属性为Rank,与RankCacheStruct缓存一起使用;

示例: 聊天功能,聊天数据以MsgType分类在Redis中存储

publicenumMsgType{/// <summary>/// 1:世界/// </summary>Word=1,/// <summary>/// 2:联盟/// </summary>Union,/// <summary>/// 3:私聊/// </summary>Whisper,/// <summary>/// 4:其它/// </summary>Other}[ProtoContract][EntityTable(CacheType.Rank,"",StorageType=StorageType.ReadWriteRedis)]publicclassChatInfo:RankEntity{publicoverridestringKey{get{returnMsgType.ToString();}}[ProtoMember(1)]publicMsgTypeMsgType{get;set;}[ProtoMember(2)]publicstringMessage{get;set;}[ProtoMember(3)]publicDateTimeSendTime{get;set;}}

使用RankCacheStruct操作RankEntity实体,如:

varcache=newRankCacheStruct<ChatInfo>();varchatInfo=newChatInfo(){MsgType=MsgType.Word,Message="Hello",Score=DateTime.Now.Ticks};cache.AddRank(chatData.Key,chatInfo);//根据Score字段插入排序//取前50条记录List<ChatInfo>items;if(cache.TryTakeRank(msgType,50,outitems)){}

还有ExchangeRank方法将名次1与2的两个对象对调。

日志(LogEntity)

日志类型的实体是不需要在cache中存储的,需要配制EntityTable的AccessLevel属性为只写的,直接使用DataSyncQueueManager类更新到数据库或redis库,成员中必须包含一个主键属性,可以设置数据库自增;

在数据库中表是以年月的方式存储,当数据增长过大需要清理数据库时,可以将旧月份的表直接删除再收缩数据库,默认预先创建3个月的表;

[EntityTable(AccessLevel.WriteOnly,"{数据库连接配置键}"})]publicclassMyLog:LogEntity{[EntityField(true,IsIdentity=true,IdentityNo=100)]publiclongLogId{get;set;}}

实体的Object类型成员

定义实体的成员属性可以包括:基础类型(byte、bool、short、int、long、float、double、decimal、string和枚举等),以及EntityChangeEvent子类对象,CacheList列表和CacheDictionary字典等类型;

基础类型的成员属性的定义就不详细介绍了,这里主要介绍下如何定义自定的对象类型成员;

EntityChangeEvent子类对象

它作为Entity成员属性中的自定定义类型,Json序列化存储在表的字段中,继承它的子类对象将具有修改通知事件,修改后的属性会同步到Redis或数据库中。

定义背包项BagItem子类

[ProtoContract]publicclassBagItem:EntityChangeEvent{[ProtoMember(1)]publicintId{get;set;}[ProtoMember(2)]publicintNum{get;set;}}

定义玩家UserBag背包实体,自定义成员属性需要配置isJsonSerialize为true,且数据库列的类型为Text(在mysql中text与longtext有区分,mssql不区分),

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicBagItemItem{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheList列表对象

同以上背包示例,修改成列表结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheDictionary字典对象

同以上背包示例,修改成字典结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheDictionary<int,BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

开发小提示

  • 在Enitty属性被修改时,需要额外做些事情,那么开发者要如何处理呢?

开发者首先都会想到直接在Enitty的属性里修改,为了保持Entity的干净,尽量不增加的其它方法,可以在CsScript目录下增加一层实体操作层(Controller),负责对实体的增册改查操作;

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
ScutGame edited this page Oct 30, 2015 · 11 revisions

此章节介绍如何设计服务端的实体Entity结构

目的与用途

实体是游戏中基本的存储单元,它可以通过Protobuf序列化的方式存储在Redis库中;还可以通过实体定义的Schema信息生成数据库的表结构,并且保存到数据库中。

实体Schema架构信息

使用如下定制属性提供实体Schema架构信息,如下属性:

  • EntityTable:提供数据库连接串,表名,索引,以及存储方式和Cache结构类型等信息,只能放在类定义上面,EntityTable只能存在一个;
  • EntityField:提供数据库表的字段,主键,字段类型,长度,唯一键和定义对象序列化方式,只能放在成员属性上面,EntityField只能存在一个;
  • EntityFieldExtend:定义实体的字段是可以被子类继承的,只能放在成员属性上面,EntityFieldExtend只能存在一个;

序列化标记属性

  • ProtoContract:Protobuf提供的序列化类配置;
  • ProtoMember:Protobuf提供的序列化成员属性配置,需要提供Tag标签参数,Tag在类与子类中的成员都必须唯一,不能重复,否则序列化会失败;
  • JsonIgnore: Json提供的序列化成员配置,有此标记的成员将不被序列化,其它的成员都可序列化,与Protobuf不同;

EntityTable属性

构造函数参数:

  • connectKey:数据库连接串配置键值,读取Config配置文件中connectionStrings节点的name值;

  • tableName:映射数据库中的表名, 为空时取类名作为表名;

  • periodTime:配置数据加载到缓存中过期时间多久,0表示不过期,单位秒;

  • personalName:指定玩家ID在数据库表中定义的字段名,默认是UserId,不区别大小写,配合定义BaseEntity类型的实体使用;

  • accessLevel:指定实体的访问级别,设置为只读时缓存修改不会自动更新到数据库或Redis中,ReadOnly是给策划配置类型的数值;

  • cacheType:指定使用的缓存结构类型,分为单个实体方式存储,字典方式存储(使用GetIdentity方法作为字典的Key),队列方式存储(在聊天模型中使用);

  • isStoreInDb:这个在6.7.9.6以上已经不在使用;

扩展成员属性:

使用"成员属性名="设置值

  • IncreaseStartNo:设置自增方式的主键的起始编号,此编号不同于数据库中的自增编号;

  • IsExpired:设置实体是否会过期的,配合periodTime使用;

  • StorageType:设置缓存的数据从Redis或数据库加载方式,以及是否可以同步到Redis或数据库,使用 '|'或运算符号可以配置多个;

  • Capacity:指定从数据库加载数据的数量,同数据库中的Top语句;

  • Condition:指定从数据库加载数据的过滤条件,同数据库中的where语句;

  • OrderColumn:指定从数据库加载数据的排序方式,同数据库中的orderby语句;

  • TableNameFormat:自定表生成规则,在游戏中针对游戏日志以年月方式归档存储使用;

  • Indexs: 提供创建数据库表的索引配置,允许创建多组索引,其中字符串可以使用','来设置复合索引字段;

  • 示例

(1) 创建一个UserId的索引和以多列"Col1,Col2"组合的索引;

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,Indexs=new[]{"UserID","Col1,Col2"})]publicclassMyEntityLog:LogEntity{[EntityField(true,IsIdentity=true)]publiclongLogId{get;set;}[EntityField]publicintUserID{get;set;}[EntityField]publicintCol1{get;set;}[EntityField]publicintCol2{get;set;}}

(2) 创建日志表,"Log_"是固定字符,“$date[yyyyMMdd]"是日期表达式,"{0}"是Model的名称,如果一年中的第几周表示"$week",如:"Log_$date[yyyyMM]$week_{0}";

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,TableNameFormat="Log_$date[yyyyMMdd]_{0}")]publicclassMyEntityLog:LogEntity{}

(3) 从数据库查找UserId为1380000,取前100条,并以ID降序

[EntityTable(AccessLevel.ReadWrite,DbCofingKeys.DataKey,Capacity=100,Condition="UserId=1380000",OrderColumn="ID DESC",StorageType=StorageType.ReadWriteDB ")]publicclassMyEntityData:BaseEntity{[EntityField(true)]publicintID{get;set;}[EntityField(true)]publicintUserId{get;set;}}

EntityField属性

构造函数参数:

  • fieldName:设置数据库表中的字段名,为空时取属性的名称;

  • isKey:设置数据库表中的主键,当多个属性都设置为True时,表示主键是复合主键;

  • isJsonSerialize:设置属性是Ojbect类型的字段且可Json序列化的;

  • dbType:设置属性在数据库表中的字段类型,如果未设置则根据属性的类型自动匹配;

扩展成员属性:

使用"成员属性名="设置值

  • ColumnLength:设置数据库表中字段的长度,varchar默认值为255;

  • ColumnScale:设置数据库表中字段为符点数时保留的小数位数;

  • Isnullable:设置数据库表中字段是否可为空值;

  • IsUnique:设置数据库表中字段的唯一约束,多个属性都配置时表示复合唯一约束,暂不支持多组唯一约束配置;

  • IsIdentity:设置数据库表中字段是可自增的字段, 设置为true后,此对象将不能Update的方式同步到数据了,可以Insert和Delete

  • IdentityNo:设置数据库表中可自增的字段的起始值,与IsIdentity一起使用;

  • Disable:设置字段不从数据库中取值;

  • JsonDateTimeFormat:设置字段为Object类型时,Json序列化的日期格式,与isJsonSerialize配合使用;

EntityFieldExtend属性

设置类的属性可以在子类中使用,是可继承的。

  • 示例

定义Col1与Col2属性是可以被子类继承的;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntity:BaseEntity{[EntityField]publicintUserID{get;set;}[EntityField][EntityFieldExtend]publicintCol1{get;set;}[EntityField][EntityFieldExtend]publicintCol2{get;set;}}

在MyEntityExtend类中包括父亲的Col1与Col2属性;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntityExtend:MyEntity{[EntityField]publicintUserID{get;set;}[EntityField]publicintCol3{get;set;}}

实体定义

实体的结构

结构包括:私有(BaseEntity)、共享(ShareEntity)、排名(RankEntity)、日志(LogEntity)类型;其中共享以分为只读和读写类型;共享只读类型主要是定义策划的数值配置。

私有(BaseEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Dictionary,与PersonalCacheStruct缓存一起使用,成员中必须包含以UserId为名称的主键字段;

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassMyEntity:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

共享(ShareEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Entity,与PersonalCacheStruct缓存一起使用,成员中必须包含一个主键属性;

[ProtoContract][EntityTable(CacheType.Entity,"{数据库连接配置键}"})]publicclassEntityData:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

只读类型

需要配置EntityTable的AccessLevel为ReadOnly,此实体不能写,只从数据库中读取,可以不定义序列化标记,定义也不会有错;

[ProtoContract][EntityTable(AccessLevel.ReadOnly,"{数据库连接配置键}"})]publicclassEntityInfo:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

排名(RankEntity)

此实体子类是针对有序类型的数据,需要配置EntityTable的CacheType属性为Rank,与RankCacheStruct缓存一起使用;

示例: 聊天功能,聊天数据以MsgType分类在Redis中存储

publicenumMsgType{/// <summary>/// 1:世界/// </summary>Word=1,/// <summary>/// 2:联盟/// </summary>Union,/// <summary>/// 3:私聊/// </summary>Whisper,/// <summary>/// 4:其它/// </summary>Other}[ProtoContract][EntityTable(CacheType.Rank,"",StorageType=StorageType.ReadWriteRedis)]publicclassChatInfo:RankEntity{publicoverridestringKey{get{returnMsgType.ToString();}}[ProtoMember(1)]publicMsgTypeMsgType{get;set;}[ProtoMember(2)]publicstringMessage{get;set;}[ProtoMember(3)]publicDateTimeSendTime{get;set;}}

使用RankCacheStruct操作RankEntity实体,如:

varcache=newRankCacheStruct<ChatInfo>();varchatInfo=newChatInfo(){MsgType=MsgType.Word,Message="Hello",Score=DateTime.Now.Ticks};cache.AddRank(chatData.Key,chatInfo);//根据Score字段插入排序//取前50条记录List<ChatInfo>items;if(cache.TryTakeRank(msgType,50,outitems)){}

还有ExchangeRank方法将名次1与2的两个对象对调。

日志(LogEntity)

日志类型的实体是不需要在cache中存储的,需要配制EntityTable的AccessLevel属性为只写的,直接使用DataSyncQueueManager类更新到数据库或redis库,成员中必须包含一个主键属性,可以设置数据库自增;

在数据库中表是以年月的方式存储,当数据增长过大需要清理数据库时,可以将旧月份的表直接删除再收缩数据库,默认预先创建3个月的表;

[EntityTable(AccessLevel.WriteOnly,"{数据库连接配置键}"})]publicclassMyLog:LogEntity{[EntityField(true,IsIdentity=true,IdentityNo=100)]publiclongLogId{get;set;}}

实体的Object类型成员

定义实体的成员属性可以包括:基础类型(byte、bool、short、int、long、float、double、decimal、string和枚举等),以及EntityChangeEvent子类对象,CacheList列表和CacheDictionary字典等类型;

基础类型的成员属性的定义就不详细介绍了,这里主要介绍下如何定义自定的对象类型成员;

EntityChangeEvent子类对象

它作为Entity成员属性中的自定定义类型,Json序列化存储在表的字段中,继承它的子类对象将具有修改通知事件,修改后的属性会同步到Redis或数据库中。

定义背包项BagItem子类

[ProtoContract]publicclassBagItem:EntityChangeEvent{[ProtoMember(1)]publicintId{get;set;}[ProtoMember(2)]publicintNum{get;set;}}

定义玩家UserBag背包实体,自定义成员属性需要配置isJsonSerialize为true,且数据库列的类型为Text(在mysql中text与longtext有区分,mssql不区分),

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicBagItemItem{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheList列表对象

同以上背包示例,修改成列表结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheDictionary字典对象

同以上背包示例,修改成字典结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheDictionary<int,BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

开发小提示

  • 在Enitty属性被修改时,需要额外做些事情,那么开发者要如何处理呢?

开发者首先都会想到直接在Enitty的属性里修改,为了保持Entity的干净,尽量不增加的其它方法,可以在CsScript目录下增加一层实体操作层(Controller),负责对实体的增册改查操作;

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
ScutGame edited this page Oct 30, 2015 · 11 revisions

此章节介绍如何设计服务端的实体Entity结构

目的与用途

实体是游戏中基本的存储单元,它可以通过Protobuf序列化的方式存储在Redis库中;还可以通过实体定义的Schema信息生成数据库的表结构,并且保存到数据库中。

实体Schema架构信息

使用如下定制属性提供实体Schema架构信息,如下属性:

  • EntityTable:提供数据库连接串,表名,索引,以及存储方式和Cache结构类型等信息,只能放在类定义上面,EntityTable只能存在一个;
  • EntityField:提供数据库表的字段,主键,字段类型,长度,唯一键和定义对象序列化方式,只能放在成员属性上面,EntityField只能存在一个;
  • EntityFieldExtend:定义实体的字段是可以被子类继承的,只能放在成员属性上面,EntityFieldExtend只能存在一个;

序列化标记属性

  • ProtoContract:Protobuf提供的序列化类配置;
  • ProtoMember:Protobuf提供的序列化成员属性配置,需要提供Tag标签参数,Tag在类与子类中的成员都必须唯一,不能重复,否则序列化会失败;
  • JsonIgnore: Json提供的序列化成员配置,有此标记的成员将不被序列化,其它的成员都可序列化,与Protobuf不同;

EntityTable属性

构造函数参数:

  • connectKey:数据库连接串配置键值,读取Config配置文件中connectionStrings节点的name值;

  • tableName:映射数据库中的表名, 为空时取类名作为表名;

  • periodTime:配置数据加载到缓存中过期时间多久,0表示不过期,单位秒;

  • personalName:指定玩家ID在数据库表中定义的字段名,默认是UserId,不区别大小写,配合定义BaseEntity类型的实体使用;

  • accessLevel:指定实体的访问级别,设置为只读时缓存修改不会自动更新到数据库或Redis中,ReadOnly是给策划配置类型的数值;

  • cacheType:指定使用的缓存结构类型,分为单个实体方式存储,字典方式存储(使用GetIdentity方法作为字典的Key),队列方式存储(在聊天模型中使用);

  • isStoreInDb:这个在6.7.9.6以上已经不在使用;

扩展成员属性:

使用"成员属性名="设置值

  • IncreaseStartNo:设置自增方式的主键的起始编号,此编号不同于数据库中的自增编号;

  • IsExpired:设置实体是否会过期的,配合periodTime使用;

  • StorageType:设置缓存的数据从Redis或数据库加载方式,以及是否可以同步到Redis或数据库,使用 '|'或运算符号可以配置多个;

  • Capacity:指定从数据库加载数据的数量,同数据库中的Top语句;

  • Condition:指定从数据库加载数据的过滤条件,同数据库中的where语句;

  • OrderColumn:指定从数据库加载数据的排序方式,同数据库中的orderby语句;

  • TableNameFormat:自定表生成规则,在游戏中针对游戏日志以年月方式归档存储使用;

  • Indexs: 提供创建数据库表的索引配置,允许创建多组索引,其中字符串可以使用','来设置复合索引字段;

  • 示例

(1) 创建一个UserId的索引和以多列"Col1,Col2"组合的索引;

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,Indexs=new[]{"UserID","Col1,Col2"})]publicclassMyEntityLog:LogEntity{[EntityField(true,IsIdentity=true)]publiclongLogId{get;set;}[EntityField]publicintUserID{get;set;}[EntityField]publicintCol1{get;set;}[EntityField]publicintCol2{get;set;}}

(2) 创建日志表,"Log_"是固定字符,“$date[yyyyMMdd]"是日期表达式,"{0}"是Model的名称,如果一年中的第几周表示"$week",如:"Log_$date[yyyyMM]$week_{0}";

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,TableNameFormat="Log_$date[yyyyMMdd]_{0}")]publicclassMyEntityLog:LogEntity{}

(3) 从数据库查找UserId为1380000,取前100条,并以ID降序

[EntityTable(AccessLevel.ReadWrite,DbCofingKeys.DataKey,Capacity=100,Condition="UserId=1380000",OrderColumn="ID DESC",StorageType=StorageType.ReadWriteDB ")]publicclassMyEntityData:BaseEntity{[EntityField(true)]publicintID{get;set;}[EntityField(true)]publicintUserId{get;set;}}

EntityField属性

构造函数参数:

  • fieldName:设置数据库表中的字段名,为空时取属性的名称;

  • isKey:设置数据库表中的主键,当多个属性都设置为True时,表示主键是复合主键;

  • isJsonSerialize:设置属性是Ojbect类型的字段且可Json序列化的;

  • dbType:设置属性在数据库表中的字段类型,如果未设置则根据属性的类型自动匹配;

扩展成员属性:

使用"成员属性名="设置值

  • ColumnLength:设置数据库表中字段的长度,varchar默认值为255;

  • ColumnScale:设置数据库表中字段为符点数时保留的小数位数;

  • Isnullable:设置数据库表中字段是否可为空值;

  • IsUnique:设置数据库表中字段的唯一约束,多个属性都配置时表示复合唯一约束,暂不支持多组唯一约束配置;

  • IsIdentity:设置数据库表中字段是可自增的字段, 设置为true后,此对象将不能Update的方式同步到数据了,可以Insert和Delete

  • IdentityNo:设置数据库表中可自增的字段的起始值,与IsIdentity一起使用;

  • Disable:设置字段不从数据库中取值;

  • JsonDateTimeFormat:设置字段为Object类型时,Json序列化的日期格式,与isJsonSerialize配合使用;

EntityFieldExtend属性

设置类的属性可以在子类中使用,是可继承的。

  • 示例

定义Col1与Col2属性是可以被子类继承的;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntity:BaseEntity{[EntityField]publicintUserID{get;set;}[EntityField][EntityFieldExtend]publicintCol1{get;set;}[EntityField][EntityFieldExtend]publicintCol2{get;set;}}

在MyEntityExtend类中包括父亲的Col1与Col2属性;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntityExtend:MyEntity{[EntityField]publicintUserID{get;set;}[EntityField]publicintCol3{get;set;}}

实体定义

实体的结构

结构包括:私有(BaseEntity)、共享(ShareEntity)、排名(RankEntity)、日志(LogEntity)类型;其中共享以分为只读和读写类型;共享只读类型主要是定义策划的数值配置。

私有(BaseEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Dictionary,与PersonalCacheStruct缓存一起使用,成员中必须包含以UserId为名称的主键字段;

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassMyEntity:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

共享(ShareEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Entity,与PersonalCacheStruct缓存一起使用,成员中必须包含一个主键属性;

[ProtoContract][EntityTable(CacheType.Entity,"{数据库连接配置键}"})]publicclassEntityData:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

只读类型

需要配置EntityTable的AccessLevel为ReadOnly,此实体不能写,只从数据库中读取,可以不定义序列化标记,定义也不会有错;

[ProtoContract][EntityTable(AccessLevel.ReadOnly,"{数据库连接配置键}"})]publicclassEntityInfo:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

排名(RankEntity)

此实体子类是针对有序类型的数据,需要配置EntityTable的CacheType属性为Rank,与RankCacheStruct缓存一起使用;

示例: 聊天功能,聊天数据以MsgType分类在Redis中存储

publicenumMsgType{/// <summary>/// 1:世界/// </summary>Word=1,/// <summary>/// 2:联盟/// </summary>Union,/// <summary>/// 3:私聊/// </summary>Whisper,/// <summary>/// 4:其它/// </summary>Other}[ProtoContract][EntityTable(CacheType.Rank,"",StorageType=StorageType.ReadWriteRedis)]publicclassChatInfo:RankEntity{publicoverridestringKey{get{returnMsgType.ToString();}}[ProtoMember(1)]publicMsgTypeMsgType{get;set;}[ProtoMember(2)]publicstringMessage{get;set;}[ProtoMember(3)]publicDateTimeSendTime{get;set;}}

使用RankCacheStruct操作RankEntity实体,如:

varcache=newRankCacheStruct<ChatInfo>();varchatInfo=newChatInfo(){MsgType=MsgType.Word,Message="Hello",Score=DateTime.Now.Ticks};cache.AddRank(chatData.Key,chatInfo);//根据Score字段插入排序//取前50条记录List<ChatInfo>items;if(cache.TryTakeRank(msgType,50,outitems)){}

还有ExchangeRank方法将名次1与2的两个对象对调。

日志(LogEntity)

日志类型的实体是不需要在cache中存储的,需要配制EntityTable的AccessLevel属性为只写的,直接使用DataSyncQueueManager类更新到数据库或redis库,成员中必须包含一个主键属性,可以设置数据库自增;

在数据库中表是以年月的方式存储,当数据增长过大需要清理数据库时,可以将旧月份的表直接删除再收缩数据库,默认预先创建3个月的表;

[EntityTable(AccessLevel.WriteOnly,"{数据库连接配置键}"})]publicclassMyLog:LogEntity{[EntityField(true,IsIdentity=true,IdentityNo=100)]publiclongLogId{get;set;}}

实体的Object类型成员

定义实体的成员属性可以包括:基础类型(byte、bool、short、int、long、float、double、decimal、string和枚举等),以及EntityChangeEvent子类对象,CacheList列表和CacheDictionary字典等类型;

基础类型的成员属性的定义就不详细介绍了,这里主要介绍下如何定义自定的对象类型成员;

EntityChangeEvent子类对象

它作为Entity成员属性中的自定定义类型,Json序列化存储在表的字段中,继承它的子类对象将具有修改通知事件,修改后的属性会同步到Redis或数据库中。

定义背包项BagItem子类

[ProtoContract]publicclassBagItem:EntityChangeEvent{[ProtoMember(1)]publicintId{get;set;}[ProtoMember(2)]publicintNum{get;set;}}

定义玩家UserBag背包实体,自定义成员属性需要配置isJsonSerialize为true,且数据库列的类型为Text(在mysql中text与longtext有区分,mssql不区分),

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicBagItemItem{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheList列表对象

同以上背包示例,修改成列表结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheDictionary字典对象

同以上背包示例,修改成字典结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheDictionary<int,BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

开发小提示

  • 在Enitty属性被修改时,需要额外做些事情,那么开发者要如何处理呢?

开发者首先都会想到直接在Enitty的属性里修改,为了保持Entity的干净,尽量不增加的其它方法,可以在CsScript目录下增加一层实体操作层(Controller),负责对实体的增册改查操作;

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
ScutGame edited this page Oct 30, 2015 · 11 revisions

此章节介绍如何设计服务端的实体Entity结构

目的与用途

实体是游戏中基本的存储单元,它可以通过Protobuf序列化的方式存储在Redis库中;还可以通过实体定义的Schema信息生成数据库的表结构,并且保存到数据库中。

实体Schema架构信息

使用如下定制属性提供实体Schema架构信息,如下属性:

  • EntityTable:提供数据库连接串,表名,索引,以及存储方式和Cache结构类型等信息,只能放在类定义上面,EntityTable只能存在一个;
  • EntityField:提供数据库表的字段,主键,字段类型,长度,唯一键和定义对象序列化方式,只能放在成员属性上面,EntityField只能存在一个;
  • EntityFieldExtend:定义实体的字段是可以被子类继承的,只能放在成员属性上面,EntityFieldExtend只能存在一个;

序列化标记属性

  • ProtoContract:Protobuf提供的序列化类配置;
  • ProtoMember:Protobuf提供的序列化成员属性配置,需要提供Tag标签参数,Tag在类与子类中的成员都必须唯一,不能重复,否则序列化会失败;
  • JsonIgnore: Json提供的序列化成员配置,有此标记的成员将不被序列化,其它的成员都可序列化,与Protobuf不同;

EntityTable属性

构造函数参数:

  • connectKey:数据库连接串配置键值,读取Config配置文件中connectionStrings节点的name值;

  • tableName:映射数据库中的表名, 为空时取类名作为表名;

  • periodTime:配置数据加载到缓存中过期时间多久,0表示不过期,单位秒;

  • personalName:指定玩家ID在数据库表中定义的字段名,默认是UserId,不区别大小写,配合定义BaseEntity类型的实体使用;

  • accessLevel:指定实体的访问级别,设置为只读时缓存修改不会自动更新到数据库或Redis中,ReadOnly是给策划配置类型的数值;

  • cacheType:指定使用的缓存结构类型,分为单个实体方式存储,字典方式存储(使用GetIdentity方法作为字典的Key),队列方式存储(在聊天模型中使用);

  • isStoreInDb:这个在6.7.9.6以上已经不在使用;

扩展成员属性:

使用"成员属性名="设置值

  • IncreaseStartNo:设置自增方式的主键的起始编号,此编号不同于数据库中的自增编号;

  • IsExpired:设置实体是否会过期的,配合periodTime使用;

  • StorageType:设置缓存的数据从Redis或数据库加载方式,以及是否可以同步到Redis或数据库,使用 '|'或运算符号可以配置多个;

  • Capacity:指定从数据库加载数据的数量,同数据库中的Top语句;

  • Condition:指定从数据库加载数据的过滤条件,同数据库中的where语句;

  • OrderColumn:指定从数据库加载数据的排序方式,同数据库中的orderby语句;

  • TableNameFormat:自定表生成规则,在游戏中针对游戏日志以年月方式归档存储使用;

  • Indexs: 提供创建数据库表的索引配置,允许创建多组索引,其中字符串可以使用','来设置复合索引字段;

  • 示例

(1) 创建一个UserId的索引和以多列"Col1,Col2"组合的索引;

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,Indexs=new[]{"UserID","Col1,Col2"})]publicclassMyEntityLog:LogEntity{[EntityField(true,IsIdentity=true)]publiclongLogId{get;set;}[EntityField]publicintUserID{get;set;}[EntityField]publicintCol1{get;set;}[EntityField]publicintCol2{get;set;}}

(2) 创建日志表,"Log_"是固定字符,“$date[yyyyMMdd]"是日期表达式,"{0}"是Model的名称,如果一年中的第几周表示"$week",如:"Log_$date[yyyyMM]$week_{0}";

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,TableNameFormat="Log_$date[yyyyMMdd]_{0}")]publicclassMyEntityLog:LogEntity{}

(3) 从数据库查找UserId为1380000,取前100条,并以ID降序

[EntityTable(AccessLevel.ReadWrite,DbCofingKeys.DataKey,Capacity=100,Condition="UserId=1380000",OrderColumn="ID DESC",StorageType=StorageType.ReadWriteDB ")]publicclassMyEntityData:BaseEntity{[EntityField(true)]publicintID{get;set;}[EntityField(true)]publicintUserId{get;set;}}

EntityField属性

构造函数参数:

  • fieldName:设置数据库表中的字段名,为空时取属性的名称;

  • isKey:设置数据库表中的主键,当多个属性都设置为True时,表示主键是复合主键;

  • isJsonSerialize:设置属性是Ojbect类型的字段且可Json序列化的;

  • dbType:设置属性在数据库表中的字段类型,如果未设置则根据属性的类型自动匹配;

扩展成员属性:

使用"成员属性名="设置值

  • ColumnLength:设置数据库表中字段的长度,varchar默认值为255;

  • ColumnScale:设置数据库表中字段为符点数时保留的小数位数;

  • Isnullable:设置数据库表中字段是否可为空值;

  • IsUnique:设置数据库表中字段的唯一约束,多个属性都配置时表示复合唯一约束,暂不支持多组唯一约束配置;

  • IsIdentity:设置数据库表中字段是可自增的字段, 设置为true后,此对象将不能Update的方式同步到数据了,可以Insert和Delete

  • IdentityNo:设置数据库表中可自增的字段的起始值,与IsIdentity一起使用;

  • Disable:设置字段不从数据库中取值;

  • JsonDateTimeFormat:设置字段为Object类型时,Json序列化的日期格式,与isJsonSerialize配合使用;

EntityFieldExtend属性

设置类的属性可以在子类中使用,是可继承的。

  • 示例

定义Col1与Col2属性是可以被子类继承的;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntity:BaseEntity{[EntityField]publicintUserID{get;set;}[EntityField][EntityFieldExtend]publicintCol1{get;set;}[EntityField][EntityFieldExtend]publicintCol2{get;set;}}

在MyEntityExtend类中包括父亲的Col1与Col2属性;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntityExtend:MyEntity{[EntityField]publicintUserID{get;set;}[EntityField]publicintCol3{get;set;}}

实体定义

实体的结构

结构包括:私有(BaseEntity)、共享(ShareEntity)、排名(RankEntity)、日志(LogEntity)类型;其中共享以分为只读和读写类型;共享只读类型主要是定义策划的数值配置。

私有(BaseEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Dictionary,与PersonalCacheStruct缓存一起使用,成员中必须包含以UserId为名称的主键字段;

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassMyEntity:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

共享(ShareEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Entity,与PersonalCacheStruct缓存一起使用,成员中必须包含一个主键属性;

[ProtoContract][EntityTable(CacheType.Entity,"{数据库连接配置键}"})]publicclassEntityData:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

只读类型

需要配置EntityTable的AccessLevel为ReadOnly,此实体不能写,只从数据库中读取,可以不定义序列化标记,定义也不会有错;

[ProtoContract][EntityTable(AccessLevel.ReadOnly,"{数据库连接配置键}"})]publicclassEntityInfo:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

排名(RankEntity)

此实体子类是针对有序类型的数据,需要配置EntityTable的CacheType属性为Rank,与RankCacheStruct缓存一起使用;

示例: 聊天功能,聊天数据以MsgType分类在Redis中存储

publicenumMsgType{/// <summary>/// 1:世界/// </summary>Word=1,/// <summary>/// 2:联盟/// </summary>Union,/// <summary>/// 3:私聊/// </summary>Whisper,/// <summary>/// 4:其它/// </summary>Other}[ProtoContract][EntityTable(CacheType.Rank,"",StorageType=StorageType.ReadWriteRedis)]publicclassChatInfo:RankEntity{publicoverridestringKey{get{returnMsgType.ToString();}}[ProtoMember(1)]publicMsgTypeMsgType{get;set;}[ProtoMember(2)]publicstringMessage{get;set;}[ProtoMember(3)]publicDateTimeSendTime{get;set;}}

使用RankCacheStruct操作RankEntity实体,如:

varcache=newRankCacheStruct<ChatInfo>();varchatInfo=newChatInfo(){MsgType=MsgType.Word,Message="Hello",Score=DateTime.Now.Ticks};cache.AddRank(chatData.Key,chatInfo);//根据Score字段插入排序//取前50条记录List<ChatInfo>items;if(cache.TryTakeRank(msgType,50,outitems)){}

还有ExchangeRank方法将名次1与2的两个对象对调。

日志(LogEntity)

日志类型的实体是不需要在cache中存储的,需要配制EntityTable的AccessLevel属性为只写的,直接使用DataSyncQueueManager类更新到数据库或redis库,成员中必须包含一个主键属性,可以设置数据库自增;

在数据库中表是以年月的方式存储,当数据增长过大需要清理数据库时,可以将旧月份的表直接删除再收缩数据库,默认预先创建3个月的表;

[EntityTable(AccessLevel.WriteOnly,"{数据库连接配置键}"})]publicclassMyLog:LogEntity{[EntityField(true,IsIdentity=true,IdentityNo=100)]publiclongLogId{get;set;}}

实体的Object类型成员

定义实体的成员属性可以包括:基础类型(byte、bool、short、int、long、float、double、decimal、string和枚举等),以及EntityChangeEvent子类对象,CacheList列表和CacheDictionary字典等类型;

基础类型的成员属性的定义就不详细介绍了,这里主要介绍下如何定义自定的对象类型成员;

EntityChangeEvent子类对象

它作为Entity成员属性中的自定定义类型,Json序列化存储在表的字段中,继承它的子类对象将具有修改通知事件,修改后的属性会同步到Redis或数据库中。

定义背包项BagItem子类

[ProtoContract]publicclassBagItem:EntityChangeEvent{[ProtoMember(1)]publicintId{get;set;}[ProtoMember(2)]publicintNum{get;set;}}

定义玩家UserBag背包实体,自定义成员属性需要配置isJsonSerialize为true,且数据库列的类型为Text(在mysql中text与longtext有区分,mssql不区分),

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicBagItemItem{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheList列表对象

同以上背包示例,修改成列表结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheDictionary字典对象

同以上背包示例,修改成字典结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheDictionary<int,BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

开发小提示

  • 在Enitty属性被修改时,需要额外做些事情,那么开发者要如何处理呢?

开发者首先都会想到直接在Enitty的属性里修改,为了保持Entity的干净,尽量不增加的其它方法,可以在CsScript目录下增加一层实体操作层(Controller),负责对实体的增册改查操作;

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
ScutGame edited this page Oct 30, 2015 · 11 revisions

此章节介绍如何设计服务端的实体Entity结构

目的与用途

实体是游戏中基本的存储单元,它可以通过Protobuf序列化的方式存储在Redis库中;还可以通过实体定义的Schema信息生成数据库的表结构,并且保存到数据库中。

实体Schema架构信息

使用如下定制属性提供实体Schema架构信息,如下属性:

  • EntityTable:提供数据库连接串,表名,索引,以及存储方式和Cache结构类型等信息,只能放在类定义上面,EntityTable只能存在一个;
  • EntityField:提供数据库表的字段,主键,字段类型,长度,唯一键和定义对象序列化方式,只能放在成员属性上面,EntityField只能存在一个;
  • EntityFieldExtend:定义实体的字段是可以被子类继承的,只能放在成员属性上面,EntityFieldExtend只能存在一个;

序列化标记属性

  • ProtoContract:Protobuf提供的序列化类配置;
  • ProtoMember:Protobuf提供的序列化成员属性配置,需要提供Tag标签参数,Tag在类与子类中的成员都必须唯一,不能重复,否则序列化会失败;
  • JsonIgnore: Json提供的序列化成员配置,有此标记的成员将不被序列化,其它的成员都可序列化,与Protobuf不同;

EntityTable属性

构造函数参数:

  • connectKey:数据库连接串配置键值,读取Config配置文件中connectionStrings节点的name值;

  • tableName:映射数据库中的表名, 为空时取类名作为表名;

  • periodTime:配置数据加载到缓存中过期时间多久,0表示不过期,单位秒;

  • personalName:指定玩家ID在数据库表中定义的字段名,默认是UserId,不区别大小写,配合定义BaseEntity类型的实体使用;

  • accessLevel:指定实体的访问级别,设置为只读时缓存修改不会自动更新到数据库或Redis中,ReadOnly是给策划配置类型的数值;

  • cacheType:指定使用的缓存结构类型,分为单个实体方式存储,字典方式存储(使用GetIdentity方法作为字典的Key),队列方式存储(在聊天模型中使用);

  • isStoreInDb:这个在6.7.9.6以上已经不在使用;

扩展成员属性:

使用"成员属性名="设置值

  • IncreaseStartNo:设置自增方式的主键的起始编号,此编号不同于数据库中的自增编号;

  • IsExpired:设置实体是否会过期的,配合periodTime使用;

  • StorageType:设置缓存的数据从Redis或数据库加载方式,以及是否可以同步到Redis或数据库,使用 '|'或运算符号可以配置多个;

  • Capacity:指定从数据库加载数据的数量,同数据库中的Top语句;

  • Condition:指定从数据库加载数据的过滤条件,同数据库中的where语句;

  • OrderColumn:指定从数据库加载数据的排序方式,同数据库中的orderby语句;

  • TableNameFormat:自定表生成规则,在游戏中针对游戏日志以年月方式归档存储使用;

  • Indexs: 提供创建数据库表的索引配置,允许创建多组索引,其中字符串可以使用','来设置复合索引字段;

  • 示例

(1) 创建一个UserId的索引和以多列"Col1,Col2"组合的索引;

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,Indexs=new[]{"UserID","Col1,Col2"})]publicclassMyEntityLog:LogEntity{[EntityField(true,IsIdentity=true)]publiclongLogId{get;set;}[EntityField]publicintUserID{get;set;}[EntityField]publicintCol1{get;set;}[EntityField]publicintCol2{get;set;}}

(2) 创建日志表,"Log_"是固定字符,“$date[yyyyMMdd]"是日期表达式,"{0}"是Model的名称,如果一年中的第几周表示"$week",如:"Log_$date[yyyyMM]$week_{0}";

[EntityTable(AccessLevel.WriteOnly,DbCofingKeys.LogKey,TableNameFormat="Log_$date[yyyyMMdd]_{0}")]publicclassMyEntityLog:LogEntity{}

(3) 从数据库查找UserId为1380000,取前100条,并以ID降序

[EntityTable(AccessLevel.ReadWrite,DbCofingKeys.DataKey,Capacity=100,Condition="UserId=1380000",OrderColumn="ID DESC",StorageType=StorageType.ReadWriteDB ")]publicclassMyEntityData:BaseEntity{[EntityField(true)]publicintID{get;set;}[EntityField(true)]publicintUserId{get;set;}}

EntityField属性

构造函数参数:

  • fieldName:设置数据库表中的字段名,为空时取属性的名称;

  • isKey:设置数据库表中的主键,当多个属性都设置为True时,表示主键是复合主键;

  • isJsonSerialize:设置属性是Ojbect类型的字段且可Json序列化的;

  • dbType:设置属性在数据库表中的字段类型,如果未设置则根据属性的类型自动匹配;

扩展成员属性:

使用"成员属性名="设置值

  • ColumnLength:设置数据库表中字段的长度,varchar默认值为255;

  • ColumnScale:设置数据库表中字段为符点数时保留的小数位数;

  • Isnullable:设置数据库表中字段是否可为空值;

  • IsUnique:设置数据库表中字段的唯一约束,多个属性都配置时表示复合唯一约束,暂不支持多组唯一约束配置;

  • IsIdentity:设置数据库表中字段是可自增的字段, 设置为true后,此对象将不能Update的方式同步到数据了,可以Insert和Delete

  • IdentityNo:设置数据库表中可自增的字段的起始值,与IsIdentity一起使用;

  • Disable:设置字段不从数据库中取值;

  • JsonDateTimeFormat:设置字段为Object类型时,Json序列化的日期格式,与isJsonSerialize配合使用;

EntityFieldExtend属性

设置类的属性可以在子类中使用,是可继承的。

  • 示例

定义Col1与Col2属性是可以被子类继承的;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntity:BaseEntity{[EntityField]publicintUserID{get;set;}[EntityField][EntityFieldExtend]publicintCol1{get;set;}[EntityField][EntityFieldExtend]publicintCol2{get;set;}}

在MyEntityExtend类中包括父亲的Col1与Col2属性;

[EntityTable(CacheType.Dictionary,DbCofingKeys.DataKey})]publicclassMyEntityExtend:MyEntity{[EntityField]publicintUserID{get;set;}[EntityField]publicintCol3{get;set;}}

实体定义

实体的结构

结构包括:私有(BaseEntity)、共享(ShareEntity)、排名(RankEntity)、日志(LogEntity)类型;其中共享以分为只读和读写类型;共享只读类型主要是定义策划的数值配置。

私有(BaseEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Dictionary,与PersonalCacheStruct缓存一起使用,成员中必须包含以UserId为名称的主键字段;

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassMyEntity:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

共享(ShareEntity)

此实体子类是定义属于玩家私有类型的数据,需要配置EntityTable的CacheType属性为Entity,与PersonalCacheStruct缓存一起使用,成员中必须包含一个主键属性;

[ProtoContract][EntityTable(CacheType.Entity,"{数据库连接配置键}"})]publicclassEntityData:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

只读类型

需要配置EntityTable的AccessLevel为ReadOnly,此实体不能写,只从数据库中读取,可以不定义序列化标记,定义也不会有错;

[ProtoContract][EntityTable(AccessLevel.ReadOnly,"{数据库连接配置键}"})]publicclassEntityInfo:ShareEntity{[ProtoMember(1)][EntityField(true)]publicintID{get;set;}}

排名(RankEntity)

此实体子类是针对有序类型的数据,需要配置EntityTable的CacheType属性为Rank,与RankCacheStruct缓存一起使用;

示例: 聊天功能,聊天数据以MsgType分类在Redis中存储

publicenumMsgType{/// <summary>/// 1:世界/// </summary>Word=1,/// <summary>/// 2:联盟/// </summary>Union,/// <summary>/// 3:私聊/// </summary>Whisper,/// <summary>/// 4:其它/// </summary>Other}[ProtoContract][EntityTable(CacheType.Rank,"",StorageType=StorageType.ReadWriteRedis)]publicclassChatInfo:RankEntity{publicoverridestringKey{get{returnMsgType.ToString();}}[ProtoMember(1)]publicMsgTypeMsgType{get;set;}[ProtoMember(2)]publicstringMessage{get;set;}[ProtoMember(3)]publicDateTimeSendTime{get;set;}}

使用RankCacheStruct操作RankEntity实体,如:

varcache=newRankCacheStruct<ChatInfo>();varchatInfo=newChatInfo(){MsgType=MsgType.Word,Message="Hello",Score=DateTime.Now.Ticks};cache.AddRank(chatData.Key,chatInfo);//根据Score字段插入排序//取前50条记录List<ChatInfo>items;if(cache.TryTakeRank(msgType,50,outitems)){}

还有ExchangeRank方法将名次1与2的两个对象对调。

日志(LogEntity)

日志类型的实体是不需要在cache中存储的,需要配制EntityTable的AccessLevel属性为只写的,直接使用DataSyncQueueManager类更新到数据库或redis库,成员中必须包含一个主键属性,可以设置数据库自增;

在数据库中表是以年月的方式存储,当数据增长过大需要清理数据库时,可以将旧月份的表直接删除再收缩数据库,默认预先创建3个月的表;

[EntityTable(AccessLevel.WriteOnly,"{数据库连接配置键}"})]publicclassMyLog:LogEntity{[EntityField(true,IsIdentity=true,IdentityNo=100)]publiclongLogId{get;set;}}

实体的Object类型成员

定义实体的成员属性可以包括:基础类型(byte、bool、short、int、long、float、double、decimal、string和枚举等),以及EntityChangeEvent子类对象,CacheList列表和CacheDictionary字典等类型;

基础类型的成员属性的定义就不详细介绍了,这里主要介绍下如何定义自定的对象类型成员;

EntityChangeEvent子类对象

它作为Entity成员属性中的自定定义类型,Json序列化存储在表的字段中,继承它的子类对象将具有修改通知事件,修改后的属性会同步到Redis或数据库中。

定义背包项BagItem子类

[ProtoContract]publicclassBagItem:EntityChangeEvent{[ProtoMember(1)]publicintId{get;set;}[ProtoMember(2)]publicintNum{get;set;}}

定义玩家UserBag背包实体,自定义成员属性需要配置isJsonSerialize为true,且数据库列的类型为Text(在mysql中text与longtext有区分,mssql不区分),

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicBagItemItem{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheList列表对象

同以上背包示例,修改成列表结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

CacheDictionary字典对象

同以上背包示例,修改成字典结构如下:

[ProtoContract][EntityTable(CacheType.Dictionary,"{数据库连接配置键}"})]publicclassUserBag:BaseEntity{[ProtoMember(1)][EntityField(true)]publicintUserId{get;set;}[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheDictionary<int,BagItem>Items{get;set;}protectedoverrideintGetIdentityId(){returnUserId;}}

开发小提示

  • 在Enitty属性被修改时,需要额外做些事情,那么开发者要如何处理呢?

开发者首先都会想到直接在Enitty的属性里修改,为了保持Entity的干净,尽量不增加的其它方法,可以在CsScript目录下增加一层实体操作层(Controller),负责对实体的增册改查操作;

Clone this wiki locally