bifangback-model初設(shè)計(jì)及mock數(shù)據(jù)

作一個(gè)記錄泣棋,這版bifang后端數(shù)據(jù)庫(kù)設(shè)計(jì)曙博,以極簡(jiǎn)和高度規(guī)范為原則。配置即代碼抹竹,devops潮流大勢(shì)线罕。

一,base_models

from django.db import models
from simple_history.models import HistoricalRecords
from django.contrib.auth.models import User


# 基礎(chǔ)虛類窃判,所有Model的共同字段钞楼,其它model由此繼承,包括記錄orm操作歷史的history字段兢孝。
class BaseModel(models.Model):
    # unique=True用于保證名稱不重復(fù)
    name = models.CharField(max_length=100,
                            unique=True,
                            verbose_name="名稱")
    description = models.CharField(max_length=100,
                                   null=True,
                                   blank=True,
                                   verbose_name="描述")
    # 為了避免刪除關(guān)聯(lián)記錄窿凤,bigang里所有外鍵都是on_delete=models.SET_NULL
    create_user = models.ForeignKey(User,
                                    blank=True,
                                    null=True,
                                    on_delete=models.SET_NULL,
                                    verbose_name="用戶")
    # auto_now用于orm更新記錄時(shí),每次自動(dòng)更新此字段時(shí)間
    update_date = models.DateTimeField(auto_now=True)
    # auto_now_add只用于第一次新增時(shí)跨蟹,自動(dòng)更新此字段時(shí)間
    create_date = models.DateTimeField(auto_now_add=True)
    # 用于擴(kuò)展
    base_status = models.BooleanField(default=True)
    # django的simple_history庫(kù)雳殊,加此字段,用于自動(dòng)保存每個(gè)表的操作歷史
    history = HistoricalRecords(inherit=True)

    # property用于為orm查詢?cè)黾右粋€(gè)計(jì)算型字段
    @property
    def username(self):
        return self.create_user.username

    # 記錄的默認(rèn)顯示值
    def __str__(self):
        return self.name

    class Meta:
        # abstract關(guān)鍵字窗轩,表示此class不會(huì)生成數(shù)據(jù)表夯秃,只能被繼承使用
        abstract = True
        # 默認(rèn)排序規(guī)則,按更新時(shí)間降序
        ordering = ('-update_date',)
  • django-simple-history的用法痢艺,以后再單文說(shuō)明仓洼。
  • 所有的model都繼承自這個(gè)Model,所以這個(gè)model的abstract = True。
  • name要求unique堤舒,在設(shè)計(jì)時(shí)要注意條件滿足色建。
  • property裝飾符,用于每個(gè)rest的返回值加用戶名舌缤。
  • update_date和create_date也為必有字段箕戳,保持每次更新時(shí)間和記錄新增時(shí)間。

二国撵,git_models

from django.db import models
from .base_models import BaseModel


# GitLab代碼倉(cāng)庫(kù)地址陵吸,有的公司可能有多個(gè)Git倉(cāng)庫(kù),所以獨(dú)立出一個(gè)數(shù)據(jù)表
# 如果只有一個(gè)代碼倉(cāng)庫(kù)介牙,當(dāng)然也可以直接在django的settings文件里定義
class GitTb(BaseModel):
    # gitlab的URL
    git_url = models.URLField(verbose_name="Git API地址")
    # 用于python的gitlab庫(kù)去進(jìn)行API 認(rèn)證時(shí)需要
    git_token = models.CharField(max_length=64, default='no_token', verbose_name="Git API認(rèn)證token")
    # gitlab的版本壮虫,僅于展示記錄,無(wú)實(shí)在用途
    git_ver = models.CharField(max_length=16, default='12.10', verbose_name="Git版本")

    class Meta:
        # 用于定義數(shù)據(jù)表名稱环础,而不使用系統(tǒng)自動(dòng)生成的囚似,統(tǒng)一命名為與類名相同
        db_table = 'GitTb'
        # 用一起定義admin后臺(tái)顯示名稱(規(guī)則為數(shù)據(jù)表名及簡(jiǎn)短中文)
        verbose_name = 'GitTb代碼倉(cāng)庫(kù)'
        verbose_name_plural = 'GitTb代碼倉(cāng)庫(kù)'
  • db_table均統(tǒng)一命名為與類名相同剩拢。
  • Git,Salt谆构,Jenkins這種表裸扶,為了避免與可能的第三方庫(kù)命令相同,無(wú)論是app名稱還是數(shù)據(jù)表名稱搬素,都要注意加一點(diǎn)后綴呵晨。
  • verbose_name_plural 和verbose_name相同,命名為表名+中文說(shuō)明熬尺。
  • git_token第三方庫(kù)去gitlab拉代碼時(shí)的認(rèn)證摸屠。

三,salt_models

from django.db import models
from .base_models import BaseModel


# Sat Api地址粱哼,不同環(huán)境季二,可能需要不同的Salt Master隔離
# 同樣,如果只有一個(gè)salt master揭措,只需要在django settings文件里定義
class SaltTb(BaseModel):
    # bifang項(xiàng)目主要使用了saltypie這個(gè)第三方庫(kù)操作salt-api胯舷,之前的字段,主要是滿足saltypie的認(rèn)證salt-api的參數(shù)
    salt_url = models.URLField(verbose_name="Salt API地址")
    salt_user = models.CharField(max_length=64, verbose_name="Salt API用戶")
    salt_pwd = models.CharField(max_length=64, verbose_name="Salt API密碼")
    eauth = models.CharField(max_length=64, default='pam', verbose_name="Salt API用戶認(rèn)證")
    trust_host = models.BooleanField(default=True, verbose_name="Salt API安全認(rèn)證")
    # 同樣绊含,這個(gè)版本只用于可能的展示桑嘶,現(xiàn)在沒(méi)有使用
    salt_ver = models.CharField(max_length=12, default='2019.3010', verbose_name="Salt版本")

    class Meta:
        db_table = 'SaltTb'
        verbose_name = 'SaltTb遠(yuǎn)程執(zhí)行工具'
        verbose_name_plural = 'SaltTb遠(yuǎn)程執(zhí)行工具'
  • salt和git表類似,一般公司可能是只有一個(gè)躬充,但為了能靈活逃顶,放在數(shù)據(jù)庫(kù)里配置。
  • saltypie庫(kù)的初始化如下:
    salt = Salt( url=salt_url, username=salt_user, passwd=salt_pwd, trust_host=True, eauth=eauth )

四充甚, env_models

from django.db import models
from .base_models import BaseModel
from .salt_models import SaltTb


# 開(kāi)發(fā)環(huán)境 以政,線上環(huán)境,等等
class Env(BaseModel):
    # 因?yàn)槔^續(xù)自BaseModel伴找,所以name,description,user,date那些字段都有了盈蛮,不用重復(fù)
    # 使用id,可以在必要時(shí)技矮,減少一些對(duì)數(shù)據(jù)表自增id的依賴眉反,在作數(shù)據(jù)庫(kù)遷移方面,還是有好處的
    env_id = models.IntegerField(default=0, verbose_name="環(huán)境Id")
    # 一般salt master都是分環(huán)境建的穆役,這樣能達(dá)到批量管理的目的,多套saltmaster又可以隔離安全網(wǎng)絡(luò)
    salt = models.ForeignKey(SaltTb,
                             related_name="ra_env",
                             blank=True,
                             null=True,
                             on_delete=models.SET_NULL,
                             verbose_name="Salt地址")

    class Meta:
        db_table = 'Env'
        verbose_name = 'Env環(huán)境'
        verbose_name_plural = 'Env環(huán)境'
  • 環(huán)境表梳凛,可能只需要name就可以滿足需求耿币,但為了更有擴(kuò)展性,加一個(gè)env_id韧拒,沒(méi)害處淹接。
  • 假定我們可以按環(huán)境可接入salt十性,所以這里加入一個(gè)salt的外鍵字段。
  • django 默認(rèn)每個(gè)主表的對(duì)象都有一個(gè)是外鍵的屬性塑悼,可以通過(guò)它來(lái)查詢到所有屬于主表的子表的信息劲适。這個(gè)屬性的名稱默認(rèn)是以子表的名稱小寫(xiě)加上_set()來(lái)表示,默認(rèn)返回的是一個(gè)querydict對(duì)象厢蒜。related_name 可以給這個(gè)外鍵定義好一個(gè)別的名稱霞势。
  • related_name我的命名規(guī)則,是ra_開(kāi)頭斑鸦,然后加上表名env愕贡。這樣的約定,更易理解代碼中的引用巷屿。如果有兩個(gè)字段關(guān)聯(lián)到同一個(gè)外鍵表固以,才需要再定義一個(gè)不同的related_name(如server里關(guān)聯(lián)兩個(gè)release,一個(gè)運(yùn)行發(fā)布單 嘱巾,一個(gè)可能的回滾單)憨琳。
  • blank=True,null=True,on_delete=models.SET_NULL,這三者定義是關(guān)聯(lián)的。如果我們定義了on_delete策略為models.SET_NULL旬昭,則必須定義null=True篙螟,即允許此字段為空。不然稳懒,在作數(shù)據(jù)庫(kù)的mock數(shù)據(jù)時(shí)闲擦,刪除一個(gè)表的所有記錄時(shí),會(huì)有些細(xì)節(jié)和順序要調(diào)整场梆。而如果使用這三個(gè)值墅冷,則基本無(wú)所謂順序。
  • null 是針對(duì)數(shù)據(jù)庫(kù)而言或油,如果 null=True, 表示數(shù)據(jù)庫(kù)的該字段可以為空寞忿。blank 是針對(duì)表單的,如果 blank=True顶岸,表示你的表單填寫(xiě)該字段的時(shí)候可以不填腔彰。

五,project_models

from django.db import models
from .base_models import BaseModel


# 項(xiàng)目辖佣,可表示由多個(gè)相關(guān)微服務(wù)組成的項(xiàng)目
# 比如霹抛,istio里的bookin就算是bifang中的一個(gè)project
# 而其中的productpage,reviews,details,ratings這些應(yīng)用,就相當(dāng)于bifang中的app應(yīng)用
class Project(BaseModel):
    # 只是為了一個(gè)中文顯示卷谈,及統(tǒng)一的項(xiàng)目id加的杯拐。不解釋
    cn_name = models.CharField(max_length=255, verbose_name="中文名")
    project_id = models.IntegerField(default=0, verbose_name="項(xiàng)目編號(hào)")

    class Meta:
        db_table = 'Project'
        verbose_name = 'Project項(xiàng)目'
        verbose_name_plural = 'Project項(xiàng)目'

  • 無(wú)須解釋,可以用來(lái)表示多含多個(gè)組件的項(xiàng)目,也可以用來(lái)表達(dá)一個(gè)含有很多微服務(wù)的項(xiàng)目總和端逼,方便分類和管理朗兵。
  • 一般項(xiàng)目命令為英文字母,加一個(gè)中文別名和項(xiàng)目id顶滩,更易溝通和語(yǔ)義定義余掖。
  • istio的bookinfo項(xiàng)目詳情:http://www.reibang.com/c/8572c5dbba97

六,app_models

from django.db import models
from .base_models import BaseModel
from .git_models import GitTb
from .project_models import Project


# 應(yīng)用服務(wù)礁鲁,相當(dāng)于一個(gè)可獨(dú)立部署的微服務(wù)
# 如istio bookinfo中盐欺,可獨(dú)立部署,且使用不同語(yǔ)言開(kāi)發(fā)的4個(gè)應(yīng)用(productpage, details, reviews, ratings)
class App(BaseModel):
    # cn_name和app_id意義和project中的一樣救氯。但要注意找田,這個(gè)app_id,會(huì)和一些外鍵表中的app_id有雷同着憨,這是高級(jí)技巧了墩衙,
    # 這是一個(gè)坑,但本來(lái)這樣命名甲抖,也是最自然的漆改,無(wú)所畏懼。
    cn_name = models.CharField(max_length=255, verbose_name="中文名")
    app_id = models.IntegerField(default=0, verbose_name="應(yīng)用編號(hào)")
    # 每個(gè)app應(yīng)用准谚,與一個(gè)代碼關(guān)聯(lián)
    git = models.ForeignKey(GitTb,
                            related_name="ra_app",
                            blank=True,
                            null=True,
                            on_delete=models.SET_NULL,
                            verbose_name="Git實(shí)例")
    # 這里單獨(dú)定義一個(gè)git中這個(gè)app的id挫剑,可能有優(yōu)化的空間,也可能沒(méi)有柱衔。git庫(kù)在定義具體的代碼倉(cāng)庫(kù)時(shí)樊破,就是要這個(gè)參數(shù)
    git_app_id = models.IntegerField(default=0, verbose_name="Git應(yīng)用ID")
    # 為了加強(qiáng)控制,git中每一個(gè)ci/cd功能唆铐,當(dāng)代碼提交之后哲戚,都不會(huì)自動(dòng)運(yùn)行,而要通過(guò)一個(gè)trigger token去人工觸發(fā)
    # 這個(gè)trigger token的生成艾岂,在gitlab的ci/cd里顺少,很容易生成。
    git_trigger_token = models.CharField(max_length=64,
                                         blank=True,
                                         null=True,
                                         verbose_name="git trigger token")
    # 將app與project進(jìn)行關(guān)聯(lián)王浴,方便數(shù)據(jù)統(tǒng)計(jì)脆炎,關(guān)聯(lián)顯示
    project = models.ForeignKey(Project,
                                related_name='ra_project',
                                blank=True,
                                null=True,
                                on_delete=models.SET_NULL,
                                verbose_name='項(xiàng)目')
    # 按devops及gitops的理念,開(kāi)發(fā)維護(hù)自己的編譯腳本和部署腳本氓辣,并進(jìn)行版本管理
    # build_script用于定義通過(guò)構(gòu)建秒裕,生成軟件包的腳本
    build_script = models.CharField(max_length=255, default='build.sh', verbose_name="編譯腳本名")
    # deploy_script用于定義服務(wù)部署,啟停钞啸,備份几蜻,回滾癞松,健康狀態(tài)檢測(cè)等功能,
    # 如果為開(kāi)發(fā)提供了模板入蛆,是很容易作為代碼一部份管理起來(lái)的,配置即代碼硕勿!
    # 如果bifang本身來(lái)存儲(chǔ)這些腳本哨毁,反而增加管理難度,不透明度及中心化
    deploy_script = models.CharField(max_length=255, default='bifang.sh', verbose_name="部署腳本名")
    # 定義軟件包的名稱源武,這里也有糾結(jié)扼褪,是自定義,還是強(qiáng)約定
    # 如果規(guī)定了軟件包名必須與app名稱相同粱栖,會(huì)少很多事话浇,但又顯得過(guò)于霸道
    # 這里留個(gè)小口吧,另外闹究,在部署腳本時(shí)幔崖,還會(huì)有幾個(gè)類似的軟件包,軟件壓縮包名渣淤,軟件部署目錄赏寇,都類似
    zip_package_name = models.CharField(max_length=255, default='demo.zio', verbose_name="應(yīng)用壓縮包")
    # 如果app有腳本端口,則可能用于狀態(tài)檢測(cè)价认,增加部署的成功判斷率
    service_port = models.IntegerField(default=0, verbose_name="服務(wù)端口")
    # 使用哪個(gè)用戶名和哪個(gè)用戶組啟動(dòng)嗅定,
    service_username = models.CharField(max_length=24,
                                        blank=True,
                                        null=True,
                                        verbose_name="執(zhí)行用戶名")
    service_group = models.CharField(max_length=24,
                                     blank=True,
                                     null=True,
                                     verbose_name="執(zhí)行用戶組")
    # 如果bifang增加獨(dú)立的服務(wù)器啟停功能,日志單獨(dú)數(shù)據(jù)表保存用踩,但這里可以保存最近啟停次數(shù)渠退,用于定義日志
    op_log_no = models.IntegerField(default=0, verbose_name="啟停日志次數(shù)")

    class Meta:
        db_table = 'App'
        verbose_name = 'App應(yīng)用'
        verbose_name_plural = 'App應(yīng)用'
  • cn_name和app_id,和Project字段同義脐彩。
  • 每個(gè)app屬于一個(gè)project碎乃。所以它們之間有一對(duì)多的關(guān)系。
  • 使用django model orm的外鍵定義丁屎,主要是在查詢方便荠锭,有很強(qiáng)大的語(yǔ)法支持。在更新時(shí)晨川,orm更易和python的數(shù)據(jù)結(jié)構(gòu)交互证九。
  • git和git_repo用于定義此app的git地址,方便bifang對(duì)此代碼庫(kù)進(jìn)行ci/cd操作共虑。
  • service_username和service_group愧怜,用于調(diào)用salt進(jìn)行遠(yuǎn)程部署時(shí),指定用戶名和用戶組妈拌。一般來(lái)說(shuō)拥坛,用戶名足夠了蓬蝶。但,萬(wàn)一有用戶的要求呢猜惋?留有擴(kuò)展丸氛。
  • op_log_no 字段,用于定位此app組件的啟停批次著摔,方便查看日志時(shí)的定義缓窜。當(dāng)然,也可以在此app組件下的每個(gè)server中谍咆,使用此字段禾锤。但那樣ms管理有點(diǎn)不集中了。待優(yōu)化~

七摹察,server_models

from django.db import models
from .base_models import BaseModel
from .app_models import App
from .release_models import Release

SYSTEM_CHOICES = (
    ('WINDOWS', 'WINDOWS'),
    ('LINUX', 'LINUX'),
)


# 部署服務(wù)器恩掷,保證ip和port結(jié)合起來(lái)的唯一性,可以一個(gè)服務(wù)器上部署多個(gè)應(yīng)用
# 當(dāng)然供嚎,能在同一個(gè)服務(wù)器上黄娘,部署多個(gè)相同的應(yīng)用,這得益于將部署腳本讓開(kāi)發(fā)自己維護(hù)查坪。
# 真正的devops團(tuán)隊(duì)寸宏,是需要都有開(kāi)發(fā)和運(yùn)維的跨界實(shí)力的啦~
class Server(BaseModel):
    # GenericIPAddressField的字段,讓這里只存儲(chǔ)ip地址
    ip = models.GenericIPAddressField(max_length=64, verbose_name="服務(wù)器Ip")
    # 服務(wù)端口偿曙,其實(shí)氮凝,這里是需要優(yōu)化的,
    # 如果有的服務(wù)啟動(dòng)望忆,不提供端口服務(wù)呢罩阵?亂寫(xiě)么?如何保證多個(gè)無(wú)端口服務(wù)不沖突启摄?
    port = models.IntegerField(verbose_name="服務(wù)器端口")
    system_type = models.CharField(max_length=16,
                                   choices=SYSTEM_CHOICES,
                                   default='LINUX',
                                   verbose_name="操作系統(tǒng)")
    # 此服務(wù)器與哪一個(gè)app關(guān)聯(lián)
    app = models.ForeignKey(App,
                            related_name='ra_server',
                            blank=True,
                            null=True,
                            on_delete=models.SET_NULL,
                            verbose_name='應(yīng)用服務(wù)')
    # 保存在此服務(wù)器正在運(yùn)行的app的最近發(fā)布單
    main_release = models.ForeignKey(Release,
                                     related_name='ra_server_main',
                                     blank=True,
                                     null=True,
                                     on_delete=models.SET_NULL,
                                     verbose_name='主發(fā)布單')
    # 保存在此服務(wù)器正在運(yùn)行的app的次新發(fā)布單稿壁,主要用于回滾,bifang只支持最近一次回滾歉备,不支持無(wú)限回滾
    back_release = models.ForeignKey(Release,
                                     related_name='ra_server_back',
                                     blank=True,
                                     null=True,
                                     on_delete=models.SET_NULL,
                                     verbose_name='備份發(fā)布單')

    class Meta:
        db_table = 'Server'
        # 一個(gè)服務(wù)器上傅是,部署多個(gè)應(yīng)用,保證Ip加port的唯一性蕾羊。
        unique_together = ('ip', 'port')
        verbose_name = 'Server服務(wù)器'
        verbose_name_plural = 'Server服務(wù)器'

  • unique_together喧笔,聯(lián)合約束,這樣龟再,在同一個(gè)服務(wù)器上书闸,就可以部署多個(gè)app應(yīng)用。
  • main_release 和back_release 利凑,主要用來(lái)顯示服務(wù)器最新應(yīng)用浆劲,以及回滾發(fā)布單嫌术。在每次部署時(shí),都會(huì)影響這兩個(gè)字段牌借。但只是停啟服務(wù)時(shí)度气,則不會(huì)更新這兩個(gè)字段。

八膨报,release_models

from django.db import models
from .base_models import BaseModel
from .app_models import App
from .env_models import Env


# 發(fā)布單狀態(tài)蚯嫌,為了能動(dòng)態(tài)管理,我覺(jué)得加個(gè)單獨(dú)的表丙躏,有必要。
class ReleaseStatus(BaseModel):
    # ['Create', 'Building', 'BuildFailed', 'Build', 'Ready', 'Ongoing', 'Success', 'Failed']
    # ['創(chuàng)建', '編譯中', '編譯失敗', '編譯成功', '準(zhǔn)備部署', '部署中', '部署成功', '部署失敗']
    status_value = models.CharField(max_length=1024, blank=True, verbose_name="狀態(tài)值")

    class Meta:
        db_table = 'ReleaseStatus'
        verbose_name = 'ReleaseStatus發(fā)布單狀態(tài)'
        verbose_name_plural = 'ReleaseStatus發(fā)布單狀態(tài)'


# 發(fā)布單束凑, bifang部署平臺(tái)中的靈魂和紐帶
# 各種配置經(jīng)由它作融合晒旅,各種動(dòng)態(tài)數(shù)據(jù)經(jīng)由它啟動(dòng)發(fā)散
class Release(BaseModel):
    # app關(guān)聯(lián)
    app = models.ForeignKey(App,
                            related_name='ra_release',
                            blank=True,
                            null=True,
                            on_delete=models.SET_NULL,
                            verbose_name="應(yīng)用")
    # 環(huán)境關(guān)聯(lián),這個(gè)在新建和編譯發(fā)布單過(guò)程中汪诉,是沒(méi)有數(shù)據(jù)的废恋,在環(huán)境流轉(zhuǎn)之后才有
    env = models.ForeignKey(Env,
                            related_name="ra_release",
                            blank=True,
                            null=True,
                            on_delete=models.SET_NULL,
                            verbose_name="環(huán)境")
    # 如果一個(gè)發(fā)布單部署了多次,或是分批在服務(wù)器部署扒寄,就有這個(gè)記錄的必要性了鱼鼓。
    deploy_no = models.IntegerField(blank=True,
                                    null=True,
                                    default=0,
                                    verbose_name="部署次數(shù)")
    # 自定義需要部署的git分支
    git_branch = models.CharField(max_length=255,
                                  blank=True,
                                  null=True)
    # pipeline_id和pipeline_url在編譯軟件包的過(guò)程中生成,用于獲取編譯狀態(tài)及定位編譯輸出
    pipeline_id = models.IntegerField(default=0)
    pipeline_url = models.URLField(default='http://www.demo.com')
    # 這個(gè)部署腳本该编,在git代碼中的一般會(huì)有自己獨(dú)立的目錄迄本,而在制品倉(cāng)庫(kù)時(shí),可能就有軟件包并列在同一個(gè)目錄了课竣,清晰嘉赎。
    deploy_script_url = models.URLField(default=None,
                                        blank=True,
                                        null=True,
                                        verbose_name="部署腳本路徑")
    # 這里生成軟件包之后,直接記錄url軟件包路徑于樟,這樣在部署腳本中公条,就可以直接使用wget下載了。
    # 為什么不使用salt迂曲?其實(shí)也行靶橱,但wget不是更穩(wěn)定和簡(jiǎn)單么?
    zip_package_url = models.URLField(default=None,
                                      blank=True,
                                      null=True,
                                      verbose_name="壓縮制品庫(kù)路徑")
    # 記錄各種狀態(tài)用于前端顯示
    deploy_status = models.ForeignKey(ReleaseStatus,
                                      related_name='ra_release',
                                      blank=True,
                                      null=True,
                                      on_delete=models.SET_NULL,
                                      verbose_name="發(fā)布單狀態(tài)")

    class Meta:
        db_table = 'Release'
        verbose_name = 'Release發(fā)布單'
        verbose_name_plural = 'Release發(fā)布單'

  • ReleaseStatus表路捧,用于定義發(fā)布單的狀態(tài)关霸。用外表定義,比普通的字段鬓长,更有統(tǒng)一性谒拴。
  • Release發(fā)布單數(shù)據(jù)表,需和app涉波,env關(guān)聯(lián)英上。但其狀態(tài)隨時(shí)間的變化炭序,則放到后面的ReleaseHistory表中。這樣就可以在時(shí)間上串聯(lián)起操作歷史苍日,而在空間上唯一定位當(dāng)前發(fā)布單惭聂。這對(duì)于我之前的設(shè)計(jì)來(lái)說(shuō),是個(gè)改進(jìn)(唯一尷尬的是相恃,這種ReleaseHistory數(shù)據(jù)表辜纲,本身要不是記錄操作歷史?:))---更新拦耐,服務(wù)器的操作歷史耕腾,記錄在ServerHistory數(shù)據(jù)表中,完美解決杀糯。
  • git_branch 和git_commit扫俺,用于追蹤此個(gè)發(fā)布單 的代碼分支。
  • deploy_no 用于跟蹤此發(fā)布單在不同環(huán)境的部署日志固翰。
  • salt_path和nginx_url 用于定位此app組件的制品庫(kù)路徑狼纬。此路徑下,一般會(huì)有兩個(gè)文件骂际,一個(gè)制品包(war, zip...)疗琉,另一個(gè)部署腳本。salt會(huì)調(diào)用這個(gè)部署腳本歉铝,操作制品包的部署全過(guò)程盈简。這種集成的東東,對(duì)于之前的部署來(lái)說(shuō)太示,也是一個(gè)新思路送火。

九,history_models

from django.db import models
from .base_models import BaseModel
from .env_models import Env
from .release_models import Release, ReleaseStatus
from .server_models import Server

# 用于在發(fā)布單的部署代碼層次內(nèi)的記錄先匪,是部署新代碼种吸,還是回滾老代碼?
# 用于發(fā)布單歷史
DEPLOY_CHOICES = (
    ('deploy', '部署'),
    ('rollback', '回滾'),
)

# 用于大的方向呀非,是在部署代碼坚俗,還是單純?cè)诰S護(hù)服務(wù)器啟停?
# 用于服務(wù)器操作歷史岸裙,區(qū)分發(fā)布單和服務(wù)器操作歷史是有意義的猖败,維護(hù)不一樣,但也可以一追到底
OP_CHOICES = (
    ('deploy', '部署'),
    ('maintenance', '啟停維護(hù)'),
)

# 在服務(wù)器上操作的第一步作記錄降允,后期根據(jù)需要恩闻,有可能作維護(hù)改動(dòng)
# 它主要要能包括所有deploy過(guò)程中的action_list列表項(xiàng)目
ACTION_CHOICES = (
    ('fetch', '獲取軟件'),
    ('stop', '停止'),
    ('stop_status', '停止?fàn)顟B(tài)檢測(cè)'),
    ('deploy', '部署'),
    ('rollback', '回滾'),
    ('start', '啟動(dòng)'),
    ('start_status', '啟動(dòng)狀態(tài)檢測(cè)'),
    ('health_check', '服務(wù)健康檢測(cè)'),
)


# 發(fā)布單歷史,記錄發(fā)布單的生命周期剧董,新建幢尚,編譯破停,流轉(zhuǎn),部署尉剩,回滾部署
class ReleaseHistory(BaseModel):
    release = models.ForeignKey(Release,
                                related_name='ra_release_history',
                                blank=True,
                                null=True,
                                on_delete=models.SET_NULL,
                                verbose_name='發(fā)布單')
    env = models.ForeignKey(Env,
                            related_name="ra_release_history",
                            blank=True,
                            null=True,
                            on_delete=models.SET_NULL,
                            verbose_name="環(huán)境")
    deploy_status = models.ForeignKey(ReleaseStatus,
                                      related_name='ra_release_history',
                                      blank=True,
                                      null=True,
                                      on_delete=models.SET_NULL,
                                      verbose_name="發(fā)布單狀態(tài)")
    deploy_type = models.CharField(max_length=255,
                                   choices=DEPLOY_CHOICES,
                                   blank=True,
                                   null=True,
                                   verbose_name="部署類型")
    log = models.TextField(verbose_name="日志內(nèi)容")

    class Meta:
        db_table = 'ReleaseHistory'
        verbose_name = 'ReleaseHistory發(fā)布單歷史'
        verbose_name_plural = 'ReleaseHistory發(fā)布單歷史'


# 服務(wù)器變更歷史真慢,記錄服務(wù)器上的部署,停止理茎,回滾
class ServerHistory(BaseModel):
    server = models.ForeignKey(Server,
                               related_name='ra_server_history',
                               blank=True,
                               null=True,
                               on_delete=models.SET_NULL,
                               verbose_name='服務(wù)器')
    release = models.ForeignKey(Release,
                                related_name='ra_server_history',
                                blank=True,
                                null=True,
                                on_delete=models.SET_NULL,
                                verbose_name='發(fā)布單')
    env = models.ForeignKey(Env,
                            related_name="ra_server_history",
                            blank=True,
                            null=True,
                            on_delete=models.SET_NULL,
                            verbose_name="環(huán)境")
    op_type = models.CharField(max_length=255,
                               choices=OP_CHOICES,
                               blank=True,
                               null=True,
                               verbose_name="操作類型")
    action_type = models.CharField(max_length=255,
                                   choices=ACTION_CHOICES,
                                   blank=True,
                                   null=True,
                                   verbose_name="服務(wù)器操作類型")
    log = models.TextField(verbose_name="日志內(nèi)容")

    class Meta:
        db_table = 'ServerHistory'
        verbose_name = 'ServerHistory服務(wù)器歷史'
        verbose_name_plural = 'ServerHistory服務(wù)器歷史'

  • 維護(hù)兩個(gè)歷史表黑界,一個(gè)記錄發(fā)布單歷史,一個(gè)記錄服務(wù)器歷史皂林。待完善朗鸠。

十,permission_models

from django.db import models
from .base_models import BaseModel
from django.contrib.auth.models import User
from .app_models import App


# 創(chuàng)建編譯發(fā)布單础倍, 環(huán)境流轉(zhuǎn)童社,部署發(fā)布單三個(gè)大的權(quán)限,
# bifang暫不支持基于各個(gè)具體環(huán)境的細(xì)致權(quán)限
# 大家可以自己基于書(shū)上教授的技能著隆,自行實(shí)現(xiàn)
class Action(BaseModel):
    action_id = models.IntegerField(unique=True, verbose_name="權(quán)限序號(hào)")

    class Meta:
        db_table = 'Action'
        verbose_name = 'Action權(quán)限'
        verbose_name_plural = 'Action權(quán)限'


# 具體的權(quán)限數(shù)據(jù)表
# 如果要獲取某個(gè)服務(wù)的所有權(quán)限,或是某一應(yīng)用的指定權(quán)限的用戶列表呀癣,都是OK的美浦。
class Permission(BaseModel):
    # 權(quán)限與app應(yīng)用級(jí)別關(guān)聯(lián)
    app = models.ForeignKey(App,
                            related_name="ra_permission",
                            blank=True,
                            null=True,
                            on_delete=models.SET_NULL,
                            verbose_name="App應(yīng)用")
    # 權(quán)限與具體的權(quán)限動(dòng)作(創(chuàng)建編譯,環(huán)境流轉(zhuǎn)项栏,部署發(fā)布)關(guān)聯(lián)
    action = models.ForeignKey(Action,
                               related_name="ra_permission",
                               blank=True,
                               null=True,
                               on_delete=models.SET_NULL,
                               verbose_name="操作權(quán)限")
    # 權(quán)限關(guān)聯(lián)到用戶
    pm_user = models.ForeignKey(User,
                                related_name="ra_permission",
                                blank=True,
                                null=True,
                                on_delete=models.SET_NULL,
                                verbose_name="權(quán)限用戶")

    class Meta:
        db_table = 'Permission'
        verbose_name = 'Permission應(yīng)用權(quán)限'
        verbose_name_plural = 'Permission應(yīng)用權(quán)限'

  • casbin感覺(jué)一般般浦辨,可能不太適用于我這種情況,或是動(dòng)靜開(kāi)大沼沈,直接手?jǐn)]流酬。
  • 將權(quán)限分為三個(gè),一個(gè)是新建編譯權(quán)限列另,一個(gè)為環(huán)境流轉(zhuǎn)權(quán)限芽腾,一個(gè)為部署各個(gè)環(huán)境的權(quán)限(不再區(qū)分),自己的東東自己負(fù)責(zé)页衙。
  • 能編輯權(quán)限的摊滔,只有三種用戶,一個(gè)是系統(tǒng)admin店乐,一種是project的創(chuàng)建用戶艰躺,一個(gè)是app的創(chuàng)建用戶。
  • 小而美的權(quán)限眨八,也不過(guò)如彼腺兴。

十一,mock數(shù)據(jù)

from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import Group
from cmdb.models import *
import string
import random
import time
import datetime
from django.contrib.auth import get_user_model

User = get_user_model()

username = 'admin'
group_name = 'admin'


# 自定義命令廉侧,用于建立測(cè)試數(shù)據(jù)页响,很多ORM語(yǔ)句會(huì)使用
class Command(BaseCommand):
    help = 'create test data for BiFang back server.'

    def add_arguments(self, parser):
        self.stdout.write(self.style.SUCCESS('沒(méi)有額外參數(shù)篓足,新建全部模擬測(cè)試數(shù)據(jù),刪除所有舊記錄'))

    def handle(self, *args, **options):
        self.add_user()
        self.add_git()
        self.add_salt()
        self.add_env()
        self.add_project()
        self.add_app()
        self.add_server()
        self.add_release_status()
        self.add_release()
        self.add_action()
        self.add_permission()
        self.stdout.write(self.style.SUCCESS('數(shù)據(jù)重建完成拘泞,一把梭哈~~~'))
        # raise CommandError('Ok纷纫!')

    # 新建一個(gè)用戶
    def add_user(self):
        User.objects.all().delete()
        Group.objects.all().delete()
        print('delete all user and group data')
        User.objects.create_user(username='Dylan', email='user@demo.com', password="password")
        User.objects.create_user(username='Tyler', email='user@demo.com', password="password")
        User.objects.create_user(username='Kyle', email='user@demo.com', password="password")
        User.objects.create_user(username='Dakota', email='user@demo.com', password="password")
        User.objects.create_user(username='Marcus', email='user@demo.com', password="password")
        User.objects.create_user(username='Samantha', email='user@demo.com', password="password")
        User.objects.create_user(username='Kayla', email='user@demo.com', password="password")
        User.objects.create_user(username='Sydney', email='user@demo.com', password="password")
        User.objects.create_user(username='Courtney', email='user@demo.com', password="password")
        User.objects.create_user(username='Mariah', email='user@demo.com', password="password")
        User.objects.create_user(username='tom', email='user@demo.com', password="password")
        User.objects.create_user(username='mary', email='user@demo.com', password="password")
        admin = User.objects.create_superuser(username, 'admin@demon.com', 'password')
        root = User.objects.create_superuser('root', 'root@demon.com', 'password')
        admin_group = Group.objects.create(name=group_name)
        Group.objects.create(name='test')
        Group.objects.create(name='dev')
        Group.objects.create(name='operate')
        admin_users = [admin, root]
        admin_group.user_set.set(admin_users)
        self.stdout.write('用戶和用戶組重建完成。')

    # 新建一個(gè)Git倉(cāng)庫(kù)
    def add_git(self):
        GitTb.objects.all().delete()
        print('delete all GitTb data')
        create_user = User.objects.get(username=username)
        GitTb.objects.create(name='MainGit',
                             description='主要git庫(kù)',
                             create_user=create_user,
                             git_url='http://192.168.1.211:8180',
                             git_token='RbCcuLssPekyVgy24Nui')
        self.stdout.write('GitTb重建完成陪腌。')

    # 新建一個(gè)SaltApi
    def add_salt(self):
        SaltTb.objects.all().delete()
        print('delete all SaltTb data')
        create_user = User.objects.get(username=username)
        SaltTb.objects.create(name='MainSalt',
                              description='主要SaltApi',
                              create_user=create_user,
                              salt_url='https://192.168.1.211:8000',
                              salt_user='saltapi',
                              salt_pwd='saltapipwd',
                              eauth='pam',
                              trust_host=True)
        self.stdout.write('SaltTb重建完成辱魁。')

    # 新建一個(gè)環(huán)境
    def add_env(self):
        Env.objects.all().delete()
        print('delete all Env data')
        create_user = User.objects.get(username=username)
        salt = SaltTb.objects.order_by('?').first()
        env_list = ['dev', 'prd']
        for index, env in enumerate(env_list):
            Env.objects.create(name=env,
                               description=env,
                               create_user=create_user,
                               env_id=index,
                               salt=salt)
        self.stdout.write('Env重建完成。')

    # 新建demo項(xiàng)目
    def add_project(self):
        Project.objects.all().delete()
        print('delete all Project data')
        create_user = User.objects.get(username=username)
        project_name_list = ['User', 'Service', 'Store', 'Card', 'Support']
        project_cn_name_list = ['用戶管理', '服務(wù)', '庫(kù)存', '購(gòu)物車', '客服']
        for project_name, project_cn_name in zip(project_name_list, project_cn_name_list):
            Project.objects.create(name=project_name,
                                   description=project_name,
                                   cn_name=project_cn_name,
                                   create_user=create_user,
                                   project_id=random.randint(1000, 10000))
        self.stdout.write('Project重建完成诗鸭。')

    # 新建demo應(yīng)用
    def add_app(self):
        App.objects.all().delete()
        print('delete all App data')
        create_user = User.objects.get(username=username)
        app_name_list = ['User-Login', 'Service-724', 'Store-Address', 'Card-Adjust', 'Support-Admin', 'go-demo']
        app_cn_name_list = ['用戶登陸', '全天服務(wù)', '庫(kù)存地址', '購(gòu)物車調(diào)配', '客服后管', '畢方演示go示例']
        for app_name, app_cn_name in zip(app_name_list, app_cn_name_list):
            git = GitTb.objects.order_by('?').first()
            project = Project.objects.order_by('?').first()
            App.objects.create(name=app_name,
                               description=app_name,
                               cn_name=app_cn_name,
                               create_user=create_user,
                               app_id=random.randint(10000, 100000),
                               git=git,
                               git_app_id=1,
                               git_trigger_token='559fbd3381bc39100811bd00e499a7',
                               project=project,
                               build_script='script/build.sh',
                               deploy_script='script/deploy.sh',
                               zip_package_name='go-demo.tar.gz',
                               service_port=9090,
                               service_username='sky',
                               service_group='operate')
        self.stdout.write('App重建完成染簇。')

    # 新建server服務(wù)器
    def add_server(self):
        Server.objects.all().delete()
        print('delete all Server data')
        create_user = User.objects.get(username=username)
        for number in range(100):
            ip = '192.168.1.{}'.format(number)
            app = App.objects.order_by('?').first()
            Server.objects.create(name=ip,
                                  description=ip,
                                  create_user=create_user,
                                  ip=ip,
                                  port=random.randint(10000, 100000),
                                  app=app,
                                  system_type=random.choice(['WINDOWS', 'LINUX']))
        self.stdout.write('Server重建完成。')

    # 新建發(fā)布單狀態(tài)
    def add_release_status(self):
        ReleaseStatus.objects.all().delete()
        print('delete all ReleaseStatus data')
        create_user = User.objects.get(username=username)
        status_list = ['Create', 'Building', 'BuildFailed', 'Build', 'Ready', 'Ongoing', 'Success', 'Failed']
        status_value_list = ['創(chuàng)建', '編譯中', '編譯失敗', '編譯成功', '準(zhǔn)備部署', '部署中', '部署成功', '部署失敗']
        for status_name, status_value_name in zip(status_list, status_value_list):
            ReleaseStatus.objects.create(name=status_name,
                                         description=status_name,
                                         create_user=create_user,
                                         status_value=status_value_name)
        self.stdout.write('ReleaseStatus重建完成强岸。')

    # 新建demo發(fā)布單
    def add_release(self):
        Release.objects.all().delete()
        print('delete all Release data')
        create_user = User.objects.get(username=username)
        for number in range(100):
            app = App.objects.order_by('?').first()
            env = Env.objects.order_by('?').first()
            deploy_status = ReleaseStatus.objects.order_by('?').first()
            random_letter = ''.join(random.sample(string.ascii_letters, 2))

            name = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f") + random_letter.upper()
            Release.objects.create(name=name,
                                   description=name,
                                   create_user=create_user,
                                   app=app,
                                   env=env,
                                   git_branch='master',
                                   pipeline_id=0,
                                   pipeline_url='http://www.demo.com',
                                   deploy_script_url='http://192.168.1.213:8080/a/b/bifang.sh',
                                   zip_package_url='http://192.168.1.213:8080/a/b/go-demo.zip',
                                   deploy_status=deploy_status)
        self.stdout.write('Release重建完成锻弓。')

    # 新建權(quán)限
    def add_action(self):
        Action.objects.all().delete()
        print('delete all Action data')
        create_user = User.objects.get(username=username)
        Action.objects.create(name='Create',
                              description='創(chuàng)建編譯權(quán)限',
                              create_user=create_user,
                              action_id=100)
        Action.objects.create(name='Env',
                              description='環(huán)境權(quán)限',
                              create_user=create_user,
                              action_id=1000)
        Action.objects.create(name='Deploy',
                              description='部署權(quán)限',
                              create_user=create_user,
                              action_id=10000)
        self.stdout.write('Action重建完成。')

    # 新建demo應(yīng)用權(quán)限用戶表
    def add_permission(self):
        Permission.objects.all().delete()
        print('delete all Permission data')
        create_user = User.objects.get(username=username)
        for number in range(5):
            app = App.objects.order_by('?').first()
            action = Action.objects.order_by('?').first()
            pm_user = User.objects.order_by('?').first()
            name = '{}-{}-{}'.format(app.name, action.name, pm_user.username)
            Permission.objects.create(name=name,
                                      description=name,
                                      create_user=create_user,
                                      app=app,
                                      action=action,
                                      pm_user=pm_user)
        self.stdout.write('Permission重建完成蝌箍。')

  • 使用(venv) D:\Code\bifang\bifangback>python manage.py mockdb自定義命令青灼,可把這些mock數(shù)據(jù)導(dǎo)入數(shù)據(jù)庫(kù)。
  • 這里也使用了不同django orm和Python隨機(jī)選擇的技巧妓盲。

Look Django Admin

2021-01-03 14_51_03-Cmdb 管理 _ 登錄畢方(BiFang)系統(tǒng)后臺(tái).png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杂拨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子悯衬,更是在濱河造成了極大的恐慌弹沽,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筋粗,死亡現(xiàn)場(chǎng)離奇詭異策橘,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)娜亿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門丽已,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人买决,你說(shuō)我怎么就攤上這事促脉。” “怎么了策州?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵瘸味,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我够挂,道長(zhǎng)旁仿,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮枯冈,結(jié)果婚禮上毅贮,老公的妹妹穿的比我還像新娘。我一直安慰自己尘奏,他們只是感情好滩褥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著炫加,像睡著了一般瑰煎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俗孝,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天酒甸,我揣著相機(jī)與錄音,去河邊找鬼赋铝。 笑死插勤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的革骨。 我是一名探鬼主播农尖,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼良哲!你這毒婦竟也來(lái)了盛卡?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤臂外,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后喇颁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體漏健,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年橘霎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡榛瓮,死狀恐怖延柠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情外潜,我是刑警寧澤原环,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站处窥,受9級(jí)特大地震影響嘱吗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜滔驾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一谒麦、第九天 我趴在偏房一處隱蔽的房頂上張望俄讹。 院中可真熱鬧,春花似錦绕德、人聲如沸患膛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)踪蹬。三九已至,卻和暖如春城丧,著一層夾襖步出監(jiān)牢的瞬間延曙,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工亡哄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枝缔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓蚊惯,卻偏偏與公主長(zhǎng)得像愿卸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子截型,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

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

  • Python語(yǔ)言特性 1 Python的函數(shù)參數(shù)傳遞 看兩個(gè)如下例子趴荸,分析運(yùn)行結(jié)果: 代碼一: a = 1 def...
    伊森H閱讀 3,063評(píng)論 0 15
  • Python語(yǔ)言特性 1 Python的函數(shù)參數(shù)傳遞 看兩個(gè)如下例子,分析運(yùn)行結(jié)果: 代碼一: a = 1 def...
    時(shí)光清淺03閱讀 487評(píng)論 0 0
  • +++翻譯自Shuaiw's Blog, 轉(zhuǎn)載請(qǐng)注明原作者出處+++ 數(shù)據(jù)科學(xué)是一個(gè)相對(duì)較新的領(lǐng)域宦焦,沒(méi)有首選的版本...
    IntoTheVoid閱讀 2,122評(píng)論 0 1
  • 5月以來(lái)孵淘,哪怕對(duì)市場(chǎng)風(fēng)向再不敏感的人,也感覺(jué)到陣陣涼意歹篓。二級(jí)市場(chǎng)連續(xù)下挫瘫证,一級(jí)市場(chǎng)融資環(huán)境惡化,不論企業(yè)融資數(shù)量還...
    錢皓頻道閱讀 6,049評(píng)論 1 6
  • 推薦指數(shù): 6.0 書(shū)籍主旨關(guān)鍵詞:特權(quán)庄撮、焦點(diǎn)背捌、注意力、語(yǔ)言聯(lián)想洞斯、情景聯(lián)想 觀點(diǎn): 1.統(tǒng)計(jì)學(xué)現(xiàn)在叫數(shù)據(jù)分析载萌,社會(huì)...
    Jenaral閱讀 5,719評(píng)論 0 5