移動(dòng)端Model層與Server服務(wù)層自動(dòng)化

Model層自動(dòng)化

前言(純屬捎帶扯一下拍冠,后端大咖勿看)

談到Model層自動(dòng)化的產(chǎn)出我們就來(lái)從最初的地方開(kāi)始講尿这,數(shù)據(jù)庫(kù)廉丽!
無(wú)論你是用啥寫(xiě)服務(wù)器如果還手寫(xiě)Model那真的只能說(shuō)你夠low我服!不過(guò)如果是手寫(xiě)Model自動(dòng)生成數(shù)據(jù)庫(kù)那另說(shuō)妻味,總的來(lái)說(shuō)就是要么從數(shù)據(jù)庫(kù)轉(zhuǎn)實(shí)體出來(lái),要么從實(shí)體轉(zhuǎn)數(shù)據(jù)庫(kù)這才有點(diǎn)意思欣福。
上邊只是開(kāi)玩笑下面正題责球,很多時(shí)候大家都無(wú)奈沒(méi)辦法不能說(shuō)low不low,其中奧妙各有體會(huì)拓劝,通常有點(diǎn)規(guī)模的團(tuán)隊(duì)都是先定義表然后就出Model了

后端的Model層

后端數(shù)據(jù)庫(kù)和實(shí)體互轉(zhuǎn)的方案都不用Google雏逾,百度就能出來(lái)一大推,有直接讀庫(kù)生成的郑临,也有從實(shí)體轉(zhuǎn)sql生成庫(kù)的栖博,更有提前定義協(xié)議然后開(kāi)始出對(duì)應(yīng)模塊對(duì)應(yīng)語(yǔ)言的實(shí)體及sql

移動(dòng)端的Model層

先繼續(xù)談會(huì)后臺(tái)Model,這里要說(shuō)的只是最好有一個(gè)提前定義的過(guò)程厢洞,這樣一方面規(guī)范開(kāi)發(fā)流程提前想好怎樣建庫(kù)合理仇让,一方面有利于跨平臺(tái)跨語(yǔ)言的開(kāi)發(fā),有了提前定義的協(xié)議躺翻,Model和枚舉的各平臺(tái)自動(dòng)生成就方便了很多丧叽,寫(xiě)個(gè)簡(jiǎn)單的小程序即可,類(lèi)型也就那幾類(lèi)公你。關(guān)鍵真的是一勞永逸踊淳,省去了大家互相校對(duì)的過(guò)程。
自動(dòng)化生成移動(dòng)端乃至前端用的Model層說(shuō)白了就是做個(gè)類(lèi)型映射陕靠,細(xì)說(shuō)的話基本就是分為兩類(lèi)迂尝,一類(lèi)直接就著后端現(xiàn)有項(xiàng)目讀Model層的文件,然后做個(gè)類(lèi)型的映射直接導(dǎo)出移動(dòng)端需要的類(lèi)剪芥,另一類(lèi)也就是設(shè)計(jì)數(shù)據(jù)庫(kù)時(shí)先定義Model的協(xié)議垄开,然后根據(jù)協(xié)議自動(dòng)生成各個(gè)平臺(tái)需要的實(shí)體,而協(xié)議定義通常用序列化后的數(shù)據(jù)如xml(極力抵制粗俱,結(jié)構(gòu)復(fù)雜)说榆,json,pb寸认,sql文件都能干這事签财。

Model自動(dòng)化實(shí)現(xiàn)

上面說(shuō)到了xml,json偏塞,pb唱蒸,sql文件都能干這件事,但其中最容易就是json灸叼,github上搜個(gè)json class基本就能有一大片總有你想要的語(yǔ)言神汹,但json的局限在于也就能轉(zhuǎn)換一下model庆捺,當(dāng)然通過(guò)特殊定義中間轉(zhuǎn)換,枚舉啥的也能搞定屁魏,在這我推薦pb滔以,首先它就是專(zhuān)門(mén)用來(lái)定義協(xié)議的,枚舉實(shí)體不用說(shuō)都能搞定氓拼,包括默認(rèn)值設(shè)置也能寫(xiě)出來(lái)你画,而且是谷歌出品本身是im通訊協(xié)議,被它序列化的數(shù)據(jù)在上面說(shuō)的里面算是最小的桃漾,而另一方面關(guān)于轉(zhuǎn)碼參考https://github.com/google/protobuf/blob/master/docs/third_party.md 坏匪,直接開(kāi)放了各個(gè)語(yǔ)言的轉(zhuǎn)換方法,當(dāng)然你根本用不到它里面寫(xiě)的那么復(fù)雜撬统,它的里面實(shí)體可是直接帶pb轉(zhuǎn)換model方法的适滓,如果不是開(kāi)發(fā)im根本用不到,要?jiǎng)h部分源碼實(shí)現(xiàn)自己的需要也行恋追,凭迹。。苦囱。(下面講一下正經(jīng)方法)

Protobuf Convert轉(zhuǎn)碼

下面放代碼片段(反饋的人多放全的蕊苗,之所以不想放還有個(gè)原因是這邊實(shí)現(xiàn)有點(diǎn)粗)
這里以Python為例,只是因?yàn)榘惭b執(zhí)行方便所以選它
轉(zhuǎn)成OC的例子沿彭,之所以選OC因?yàn)槲揖褪莻€(gè)搞IOS的瞧柔。。哥蔚。寫(xiě)起來(lái)各個(gè)公司需求不同每次都要改改改糙箍。牵祟。。也就是這個(gè)原因懶得放全的了雹拄,因?yàn)橛玫娜苏f(shuō)到底還是需要手動(dòng)改成自己想要的,沒(méi)有通用的。摧阅。汰蓉。

#頭文件引用
import os

from optparse import OptionParser
#pb轉(zhuǎn)成方便處理的對(duì)象
from protoDef import *
#讀取pb的
from protoReader import ProtoReader as reader
#上邊兩個(gè)就不放源碼了,感興趣的人多再說(shuō),畢竟上邊的實(shí)現(xiàn)只是讀pb邏輯大家估計(jì)都有自己的好辦法

ENUM_TYPE = 'NSInteger'
#基礎(chǔ)類(lèi)型映射
typeMap = {
  'int64': 'NSNumber',
  'int32': 'NSNumber',
  'string': 'NSString',
  'bool': 'NSNumber',
  'float': 'NSNumber',
  'double': 'NSNumber',
}
#默認(rèn)值映射
defaultMap = {
  'int64': '@(%s)',
  'int32': '@(%s)',
  'string': '@"%s"',
  'bool': '@(%s)',
  'float': '@(%s)',
  'double': '@(%s)',
}

def _convertType(pClz):
  if typeMap.has_key(pClz):
    return typeMap[pClz]
  return pClz

def _convertDefault(pClz):
  if defaultMap.has_key(pClz):
    return defaultMap[pClz]
  return 'nil'
#寫(xiě)文件方法(就是一點(diǎn)點(diǎn)輸出oc的方法)
def _writeLine(outf, line = ''):
  outf.write(line + '\n')

class IOSWriter:
  def __init__(self, outDir, proto):
    self.outDir = outDir
    self.proto = proto

  def __writeMsg(self, msg):
    if isDeprecated(msg.comment):
      return
  
    self.__writeMsgH(msg)#生成.h
    self.__writeMsgM(msg)#生成.m

  #為對(duì)應(yīng)類(lèi)添加前綴做為命名空間(oc沒(méi)命名空間。沈撞。贷岸。)
  def __makeMsgName(self, msg):
    if msg.protoPkg == '不想加前綴的條件':
      return msg.name
    return msg.protoPkg.upper() + msg.name

  def __writeMsgH(self, msg):
    msgName = self.__makeMsgName(msg)
    path = os.path.join(self.outDir, msg.name)

    # .h
    outf = file(path + '.h', 'w')
    _writeLine(outf, '/*\n * generated by proto-pygen, NEVER CHANGE!!\n * source file: %s\n */\n' % self.proto.filePath)
    # comment
    _writeLine(outf, '/**\n * %s\n */' % msg.comment)
    # import
    _writeLine(outf, '#import <Foundation/Foundation.h>')
    importSet = set()
    for field in msg.fields:
      if isDeprecated(field.comment):
        continue
      if self.proto.hasMsg(field.clz) and field.clz not in importSet:
        importSet.add(field.clz)
        importMsg = self.proto.getMsg(field.clz)
        importName = self.__makeMsgName(importMsg)
        if self.__atClass(field):
          _writeLine(outf, '@class %s;' % importName)
        else:
          _writeLine(outf, '#import "%s.h"' % importName)
    _writeLine(outf)
    # declare
    _writeLine(outf, '@interface %s : NSObject' % msgName)
    # field
    for field in msg.fields:
      if isDeprecated(field.comment):
        continue
      if field.comment:
        _writeLine(outf, '/**\n * %s\n */' % field.comment)
      if field.repeated:
        if field.repeated and not typeMap.has_key(field.clz):
          fieldMsg = self.proto.getMsg(field.clz)
          fieldType = self.__makeMsgName(fieldMsg)
          _writeLine(outf, '@property(nonatomic, strong) NSMutableArray <%s*>* %s;' % (field.name, fieldType))
        else
          _writeLine(outf, '@property(nonatomic, strong) NSMutableArray * %s;' % field.name)
      elif self.proto.hasMsg(field.clz):
        fieldMsg = self.proto.getMsg(field.clz)
        fieldType = self.__makeMsgName(fieldMsg)
        if fieldMsg.kind == Proto.PROTO_MSG:
          _writeLine(outf, '@property(nonatomic, strong) %s * %s;' % (fieldType, field.name))
        elif fieldMsg.kind == Proto.PROTO_ENUM:
          _writeLine(outf, '@property(nonatomic, assign) %s %s;' % (fieldType, field.name))
      else:
        clz = _convertType(field.clz)
        _writeLine(outf, '@property(nonatomic, strong) %s * %s;' % (clz, field.name))
    # end
    _writeLine(outf, '\n@end')
    outf.close()

  def __atClass(self, field):
   return '@class' in field.comment

  def __writeMsgM(self, msg):
    msgName = self.__makeMsgName(msg)
    path = os.path.join(self.outDir, msgName)
    # .m
    outf = file(path + '.m', 'w')
    _writeLine(outf, '/*\n * generated by proto-pygen, NEVER CHANGE!!\n * source file: %s\n */\n' % self.proto.filePath)
    # import
    _writeLine(outf, '#import "%s.h"' % msgName)
    for field in msg.fields:
      if self.proto.hasMsg(field.clz) and self.__atClass(field):
        importMsg = self.proto.getMsg(field.clz)
        importName = self.__makeMsgName(importMsg)
        _writeLine(outf, '#import "%s.h"' % importName)
    _writeLine(outf, "\n@implementation %s" % msgName)
    _writeLine(outf)
    # repeated
    kvlist = [] #數(shù)組內(nèi)實(shí)體類(lèi) 數(shù)組
    dvlist = [] #默認(rèn)值 數(shù)組
    for field in msg.fields:
      if isDeprecated(field.comment):
        continue
      if typeMap.has_key(field.clz) and field.default:
        default=_convertDefault(field.default)
        dvlist.append((field.name,default))
      if field.repeated and not typeMap.has_key(field.clz):
        fieldMsg = self.proto.getMsg(field.clz)
        fieldType = self.__makeMsgName(fieldMsg)
        kvlist.append((field.name, fieldType))
    #默認(rèn)值設(shè)置
    if len(dvlist) > 0:
      _writeLine(outf, "- (id)init {")
      _writeLine(outf, "   if(self=[super init]){ ")
      for i, (name, default) in enumerate(dvlist):
        line = '''   _%s=%s''' % (name, default)
        _writeLine(outf, line)
      _writeLine(outf, "   } ")
      _writeLine(outf, "   return self;")
      _writeLine(outf, "} ")
  
    #用了YYModel轉(zhuǎn)換所以有了這個(gè)方法
    if len(kvlist) > 0:
      _writeLine(outf, "+ (NSDictionary *)modelContainerPropertyGenericClass {")
      _writeLine(outf, "  return @{")
      for i, (name, clz) in enumerate(kvlist):
        line = '''    @"%s" : [%s class]''' % (name, clz)
        if i < len(kvlist) - 1:
          line = line + ','
        _writeLine(outf, line)
      _writeLine(outf, "  };")
      _writeLine(outf, "}")


    # end
    _writeLine(outf, '\n@end')
    outf.close()

  #生成枚舉凡泣,之所以有.h .m是為了搞枚舉string
  def __writeEnum(self, enum):
    if isDeprecated(enum.comment):
      return
    self.__writeEnumH(enum)
    self.__writeEnumM(enum)

  def __writeEnumH(self, enum):
    enumName = self.__makeMsgName(enum)
    path = os.path.join(self.outDir, enumName)
    # .h
    outf = file(path + '.h', 'w')
    _writeLine(outf, '/*\n * generated by proto-pygen, NEVER CHANGE!!\n * source file: %s\n */\n' % self.proto.filePath)
    # comment
    _writeLine(outf, '/**\n * %s\n */' % enum.comment)
    # import
    _writeLine(outf, '#import <Foundation/Foundation.h>')
    _writeLine(outf)
    # declare
    _writeLine(outf, 'typedef enum {')
    # field
    fields = []
    for field in enum.fields:
      if isDeprecated(field.comment):
        continue
      fields.append(field)
    i = 0
    for field in fields:
      i += 1
      if field.comment:
        _writeLine(outf, '/**\n * %s\n */' % field.comment)
      if i == len(fields):
        _writeLine(outf, '  %s = %s' % (field.name, field.number))
      else:
        _writeLine(outf, '  %s = %s,' % (field.name, field.number))
    _writeLine(outf, '} %s;' % enumName)
    _writeLine(outf)
    _writeLine(outf, '%s %sValueOf(NSString *text);' % (enumName, enumName))
    _writeLine(outf, 'NSString* %sDescription(%s value);' % (enumName, enumName))

    outf.close()

  def __writeEnumM(self, enum):
    enumName = self.__makeMsgName(enum)
    path = os.path.join(self.outDir, enumName)
    # .m
    outf = file(path + '.m', 'w')
    _writeLine(outf, '/*\n * generated by proto-pygen, NEVER CHANGE!!\n * source file: %s\n */\n' % self.proto.filePath)
    # import
    _writeLine(outf, '#import "%s.h"' % enumName)
    _writeLine(outf)
    # valueOf
    _writeLine(outf, '%s %sValueOf(NSString *text) {' % (enumName, enumName))
    _writeLine(outf, '  if (text) {')
    fields = []
    for field in enum.fields:
      if isDeprecated(field.comment):
        continue
      fields.append(field)
    i = 0;
    for field in fields:
      if i == 0:
        _writeLine(outf, '    if ([text isEqualToString:@"%s"])' % field.name)
      else:
        _writeLine(outf, '    else if ([text isEqualToString:@"%s"])' % field.name)
      _writeLine(outf, '      return %s;' % field.name)
      i += 1
    _writeLine(outf, '  }')
    _writeLine(outf, '  return -1;')
    _writeLine(outf, '}\n')
    # description
    _writeLine(outf, 'NSString* %sDescription(%s value) {' % (enumName, enumName))
    _writeLine(outf, '  switch (value) {')
    for field in fields:
      _writeLine(outf, '    case %s:' % field.name)
      _writeLine(outf, '      return @"%s";' % field.name)
    _writeLine(outf, '  }')
    _writeLine(outf, '  return @"";')
    _writeLine(outf, '}')

    outf.close()

  def write(self):
    for msg in self.proto.definedMsgs:
      if msg.kind == Proto.PROTO_MSG:
        self.__writeMsg(msg)
      elif msg.kind == Proto.PROTO_ENUM:
        self.__writeEnum(msg)

#寫(xiě)文件到本地
if __name__ == '__main__':
  parser = OptionParser()
  parser.add_option("-r", "--root", dest="protoDir", help="root proto dir", metavar="DIR")
  parser.add_option("-f", "--file", dest="protoFile", help="input proto file", metavar="FILE")
  parser.add_option("-o", "--out", dest="out", help="output dir", metavar="DIR")

  options, args = parser.parse_args()

  if not options.protoDir:
    parser.print_help()
    parser.error('no proto dir')
  if not options.protoFile:
    parser.print_help()
    parser.error('no proto file')
  if not options.out:
    parser.print_help()
    parser.error('no out dir')

  if not os.path.exists(options.out):
    os.makedirs(options.out)

  proto = reader(options.protoDir, options.protoFile).read()
  IOSWriter(options.out, proto).write()

上邊就是Protobuf 轉(zhuǎn)Model的邏輯贺纲,有了這個(gè)前提航闺,下邊Server服務(wù)層的自動(dòng)化就有了

Server服務(wù)層自動(dòng)化

這里可以引申一下基本上寫(xiě)功能時(shí)只要分成配置類(lèi)和啟動(dòng)器這樣,根據(jù)配置類(lèi)就可以實(shí)現(xiàn)自動(dòng)化了猴誊,這里就拿IOS我這的實(shí)現(xiàn)講潦刃。
源碼地址:https://github.com/heroims/ServerAPI
這里也只是簡(jiǎn)單說(shuō)一下
ServerAPI 定義一個(gè)請(qǐng)求的地址,重試次數(shù)懈叹,返回?cái)?shù)據(jù)轉(zhuǎn)換模式
ServerAPIManager 根據(jù)ServerAPI發(fā)起請(qǐng)求
ServerAPIProtocol 定義需要實(shí)現(xiàn)的方法(為了擴(kuò)展性高乖杠,這里定義必須實(shí)現(xiàn)的協(xié)議,具體需要定制的需求通過(guò)Category實(shí)現(xiàn)相關(guān)方法)
ServerResult 返回的通用型實(shí)體包含解析的字典澄成,錯(cuò)誤信息等

總的思路就是ServerAPI來(lái)定義一個(gè)請(qǐng)求的具體內(nèi)容參數(shù)胧洒,而ServerAPIManager負(fù)責(zé)發(fā)起請(qǐng)求返回?cái)?shù)據(jù),然后就只需要繼承ServerAPI對(duì)不同請(qǐng)求具體參數(shù)直接返回具體的值即可墨状,比如requestHost略荡,resultFormat,retryTimes歉胶,timeOut汛兜,returnClass

回到Protobuf這個(gè)就相當(dāng)于定義request,但差別還是很大有了對(duì)一個(gè)API的描述通今,那么移動(dòng)端包括后端粥谬,前端都可以通過(guò)這個(gè)描述來(lái)做對(duì)應(yīng)的事情,只需要封裝一個(gè)東西去處理描述辫塌,至此就完成了Server服務(wù)層的自動(dòng)化漏策,外加說(shuō)一句后端的話為了性能可能更好的方案是根據(jù)描述生成代碼吧,用代碼寫(xiě)代碼才是正道臼氨。掺喻。。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末感耙,一起剝皮案震驚了整個(gè)濱河市褂乍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌即硼,老刑警劉巖逃片,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異只酥,居然都是意外死亡褥实,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)裂允,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)损离,“玉大人,你說(shuō)我怎么就攤上這事绝编∑欤” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵瓮增,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我哩俭,道長(zhǎng)绷跑,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任凡资,我火速辦了婚禮砸捏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘隙赁。我一直安慰自己垦藏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布伞访。 她就那樣靜靜地躺著掂骏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厚掷。 梳的紋絲不亂的頭發(fā)上弟灼,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音冒黑,去河邊找鬼田绑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛抡爹,可吹牛的內(nèi)容都是我干的掩驱。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼欧穴!你這毒婦竟也來(lái)了民逼?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤苔可,失蹤者是張志新(化名)和其女友劉穎缴挖,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體焚辅,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡映屋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了同蜻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棚点。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情馋记,我是刑警寧澤能曾,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站郊尝,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杖虾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望媒熊。 院中可真熱鬧奇适,春花似錦、人聲如沸芦鳍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)柠衅。三九已至皮仁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菲宴,已是汗流浹背魂贬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裙顽,地道東北人付燥。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像愈犹,于是被迫代替她去往敵國(guó)和親键科。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闻丑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容