Serverless架構(gòu)下Python語言實現(xiàn)一個基于人工智能的相冊小程序

AI_Album說明

項目背景

在日常生活中,我們經(jīng)常會遇到搜索照片的情況,尤其是對尋找過去很久的圖片,記憶中僅剩下零散的記憶的時候,我們檢索照片的方法通常是定位到大致的時間,然后一張一張的去查看,但是這種做法效率低下,還經(jīng)常會漏掉我們的目標(biāo)圖片,所以這個時候,就迫切需要一款可以搜索圖片的軟件,即我們可以通過簡單的文字描述,實現(xiàn)圖片的快速檢索。
近幾年微信小程序的發(fā)展速度飛快,從2017年年初,張小龍在2017微信公開課Pro上發(fā)布的小程序正式上線到目前為止,小程序已經(jīng)覆蓋了超過200個細(xì)分行業(yè),服務(wù)超過1000億人次用戶,年交易增長超過600%,創(chuàng)造超過5000億的商業(yè)價值。而小程序蓬勃發(fā)展的背后,是一群優(yōu)秀的小程序開發(fā)者的不斷貢獻(xiàn)。
本實例將會通過微信小程序,在Serverless架構(gòu)上,實現(xiàn)一款基于人工智能的相冊小工具,該款小工具可以在保證基礎(chǔ)相冊功能(新建相冊、刪除相冊、上傳圖片、查看圖片、刪除圖片)的基礎(chǔ)上,增加搜索功能,即用戶上傳圖片之后,基于Image Caption技術(shù),自動對圖片進(jìn)行描述,實現(xiàn)Image to Text的過程,當(dāng)用戶進(jìn)行搜索時,通過文本間的相似度,返回給用戶最貼近的圖片。

基礎(chǔ)設(shè)計

image

該項目設(shè)計主要擁有登錄功能、相冊新建、圖片上傳以及相關(guān)預(yù)覽功能,以及搜索功能,整體如圖所示。

image

其中注冊功能的主要作用是,通過獲取用戶的唯一Id(微信中的OpenId),來講用戶信息存儲到數(shù)據(jù)庫中,之后的所有操作,都需要根據(jù)該Id作為區(qū)分。相冊功能主要包括相冊添加、修改、刪除以及查看等功能。圖片功能包括圖片上傳功能、刪除功能、查看功能。搜索功能主要是可以查看指定標(biāo)簽對應(yīng)的圖片列表,以及指定搜索內(nèi)容對應(yīng)的列表。當(dāng)然這四個主要功能和模塊是和前端關(guān)系緊密的部分,除此之外還有后端異步操作的兩個模塊,分別是圖像壓縮功能以及圖像描述功能。

注冊功能:

注冊功能主要是用戶點擊注冊賬號之后,執(zhí)行的動作。該動作需要注意,用戶點擊注冊賬號注冊的時候要先判斷用戶是否已經(jīng)注冊過,如果已經(jīng)注冊過則默認(rèn)登陸,否則進(jìn)行注冊并登陸。當(dāng)用戶不想注冊時,可以點擊體驗程序,可以對程序大部分頁面進(jìn)行預(yù)覽。但是不能實現(xiàn)有關(guān)數(shù)據(jù)庫的增刪改查等功能。登錄功能頁面如圖所示。

image

相冊功能:

當(dāng)用戶注冊登錄之后,可以在相冊管理頁面進(jìn)行相冊相關(guān)的管理,包括編輯功能,刪除功能以及新建功能,此處在進(jìn)行添加和修改的時候,需要注意相冊名稱是否已經(jīng)存在;在進(jìn)行刪除相冊,修改相冊等操作時要判斷用戶是否有操作該相冊的權(quán)限等,如圖所示,是相冊功能相關(guān)原型圖。

image

圖片功能:

圖片功能主要包括圖片列表以及圖片獲取、圖片刪除以及圖片上傳功能,在圖片獲取與刪除的過程中,要對用戶是否有該項操作的權(quán)限進(jìn)行判斷,圖片上傳時也要判斷用戶是否有上傳到指定相冊的權(quán)限。圖片功能相關(guān)原型圖如所示。

image

圖片功能部分除了用戶側(cè)可見的功能,還有定時任務(wù),當(dāng)用戶上傳圖片之后,系統(tǒng)會在后臺異步進(jìn)行圖像壓縮以及圖像的描述,關(guān)鍵詞提取等。整體流程如圖所示。

image

搜索功能:

搜索功能指的是通過關(guān)鍵詞或者使用者的描述,可以獲得到目標(biāo)數(shù)據(jù)的過程,這一功能原型圖如圖所示。

image

這一部分的難點和重點在于通過用戶的描述,搜索到目標(biāo)數(shù)據(jù)的過程。這個過程的基本流程如圖所示。

image

開發(fā)總結(jié)

Serverless架構(gòu)可以說是目前非?;馃岬捻椖?,其憑借著按量付費、低成本運維、高效率開發(fā)等眾多優(yōu)點于一身,幫助我們的項目快速開發(fā),快速迭代。而Serverless Framework則是一個非常高效的工具,其兼容了AWS,Google Cloud以及騰訊云等多家廠商的Serverless架構(gòu),為開發(fā)者提供一個多云的開發(fā)者工具,目前以騰訊云為例,其擁有Plugin和Components兩個部分。

這兩個部分可以說是個有千秋,具體的大家可以官方說明,或者自己體驗一下。我這里我只說幾個我覺得很頭疼的問題。

  • Plugin部署到線上的函數(shù),會自動變更名字,例如我的函數(shù)是myFunction,我的服務(wù)和階段是myService-Dev,那么函數(shù)部署到線上就是myService-Dev-myFunction,這樣的函數(shù)名,很可能會讓我的函數(shù)間調(diào)用等部分產(chǎn)生很多不可控因素。例如我現(xiàn)在的環(huán)境是Dev,我函數(shù)間調(diào)用就要寫函數(shù)名是myService-Dev-myFunction,如果是我的環(huán)境是Test,此時就要寫myService-Test-myFunction,我始終覺得,我更改環(huán)境應(yīng)該只需要更改配置,而不是更深入的代碼邏輯。所以我對Plugin的這個換名字問題很煩躁;
  • Plugin也是有優(yōu)勢的,例如他有Invoke、Remove以及部署單個函數(shù)的功能,同時Plugin也有全局變量,我覺得這個更像一個開發(fā)者工具,我可以開發(fā)、部署、調(diào)用、查看一些信息、指標(biāo)以及刪除回滾等操作,都可以通過Plugin完成,這點很給力,我喜歡;
  • Components可以看作是一個組件集,這里面包括了很多的Components,可以有基礎(chǔ)的Components,例如cos、scf、apigateway等,也有一些拓展的Components,例如在cos上拓展出來的website,可以直接部署靜態(tài)網(wǎng)站等,還有一些框架級的,例如Koa,Express,這些Components說實話,真的蠻方便的,騰訊官方也是有他們的最佳實踐
  • Components除了剛才所說的支持的產(chǎn)品多,可以部署框架之外,對我來說,最大吸引力在于這個東西,部署到線上的函數(shù)名字就是我指定的名字,不會出現(xiàn)額外的東西,這個我非常看重;
  • Components相對Plugin在功能上略顯單薄,除了部署和刪除,再沒有其他,例如Plugin的Invoke,Rollback等等一切都沒有,同時,我們?nèi)绻卸鄠€東西要部署,寫到了一個Components的yaml上,那么我們每次部署都要部署所有的,如果我們認(rèn)為,我們只修改了一個函數(shù),并且不想重新部署其他函數(shù)從而注釋掉其他函數(shù),那么很抱歉告訴你,不行!他會看到你只有一個函數(shù),并且?guī)湍惆涯阕⑨尩舻暮瘮?shù)在線上刪除;
  • Components更多的定義是組件,所以每個組件就是一個東西,所以在Components上面,是沒有全局變量這一說法,這點我覺得很坑。

綜上所述的幾點,就是在除了官方文檔的描述之外,我對Plugin和Components的對比,感情真的可謂是錯綜復(fù)雜,也很期待產(chǎn)品策略可以將二者合并,或者功能對齊,否則單用Plugin,功能上是很全面了,但是產(chǎn)品支持不全面,名字變化我真的不能忍(可能很多人都不能忍),單用Components,沒有全局變量,沒有更多功能,可謂是產(chǎn)品廣度變了,便利增加了,但是功能太淡薄了,我對二者的感情,又恨又愛。

經(jīng)過了長久的思考,我覺得Plugin部署到線上會導(dǎo)致函數(shù)名字變化這個問題,我真的不能忍(或許我就是巨蟹座的強迫癥吧,哈哈哈),而且,我個人認(rèn)為,我未必就能需要到更多的功能,例如invoke,例如metrics等。所以我選擇了Components來做這個項目。

說到Components做這項目,我就遇到了第一個難題,我的配置文件怎么辦?我有很多的配置,我難道要在每個函數(shù)中寫一遍?

于是,我做了一個新的:serverless-global,是的,這個Components的功能,或者價值就是可以滿足我全局變量的需求,例如這樣寫我的全局變量:

Conf:
  component: "serverless-global"
  inputs:
    mysql_host: gz-cdb-mytest.sql.tencentcdb.com
    mysql_user: mytest
    mysql_password: mytest
    mysql_port: 62580
    mysql_db: mytest
    mini_program_app_id: mytest
    mini_program_app_secret: mytest

在使用的時候,只需要使用${}就可以引用,例如:

Album_Login:
  component: "@serverless/tencent-scf"
  inputs:
    name: Album_Login
    codeUri: ./album/login
    handler: index.main_handler
    runtime: Python3.6
    region: ap-shanghai
    environment:
      variables:
        mysql_host: ${Conf.mysql_host}
        mysql_port: ${Conf.mysql_port}
        mysql_user: ${Conf.mysql_user}
        mysql_password: ${Conf.mysql_password}
        mysql_db: ${Conf.mysql_db}

這樣,我就可以很簡單輕松加愉快的,將我的配置信息統(tǒng)一提取到了一個配置的地方。另外這里說一下,我為啥要把一些配置信息放在環(huán)境變量,而不是統(tǒng)一放在一個配置文件中,因為環(huán)境變量在SCF中,會真的打到環(huán)境中,也就是說,你可以直接取到,我個人覺得比每次創(chuàng)建實例讀取一次配置文件可能要性能好一些,可能只會好幾毫秒,但是,我還是覺得這樣做是比較優(yōu)雅的。最主要的是,相比寫到代碼中和配置到單獨的配置文件中,我這樣做之后,我可以分享我的代碼給別人,可以更好的保護(hù)的我的一些敏感信息。反正喜歡一種方法,有一萬個理由,不管充分不充分。

寫完了這個部分部分,我開始著手寫我的第一個函數(shù),注冊登錄函數(shù)。因為這是一個小程序,所以可以認(rèn)為,注冊登錄實際上就是拿著用戶的openId去數(shù)據(jù)庫查查有沒有信息,有信息的話,就執(zhí)行登錄,沒有信息的話就insert一下。那么問題來了,我這里要怎么連接我的數(shù)據(jù)庫?之所以有這樣的問題,是源自兩個因素:

  • 我們平時做項目更多時候都不是每次連接一次數(shù)據(jù)庫,很多時候,數(shù)據(jù)庫的連接是可以保持下來的,但是Serverless架構(gòu)下可以么?或者我們需要去哪里連接數(shù)據(jù)庫呢?
  • 傳統(tǒng)項目,我們做數(shù)據(jù)庫連接等,是只有一個方法就可以搞定,但是函數(shù)中,每個函數(shù)都是單獨存在的,我們每個函數(shù)都要連接一下數(shù)據(jù)庫?

針對問題1,我們來做一個實驗,我去騰訊云云函數(shù)創(chuàng)建一個test:

image

創(chuàng)建之后,我們瘋狂點擊測試按鈕,多次記錄運行日志:

第一次

START RequestId: 4facbf59-3787-11ea-8026-52540029942f

Event RequestId: 4facbf59-3787-11ea-8026-52540029942f

11111111

222222222


END RequestId: 4facbf59-3787-11ea-8026-52540029942f

Report RequestId: 4facbf59-3787-11ea-8026-52540029942f Duration:1ms Memory:128MB MaxMemoryUsed:27.3164MB

第二次

START RequestId: 7aaf7921-3787-11ea-aba7-525400e4521d

Event RequestId: 7aaf7921-3787-11ea-aba7-525400e4521d

222222222


END RequestId: 7aaf7921-3787-11ea-aba7-525400e4521d

Report RequestId: 7aaf7921-3787-11ea-aba7-525400e4521d Duration:1ms Memory:128MB MaxMemoryUsed:27.1953MB

第三次

START RequestId: 742be57a-3787-11ea-b5c5-52540047de0f

Event RequestId: 742be57a-3787-11ea-b5c5-52540047de0f

222222222


END RequestId: 742be57a-3787-11ea-b5c5-52540047de0f

Report RequestId: 742be57a-3787-11ea-b5c5-52540047de0f Duration:1ms Memory:128MB MaxMemoryUsed:27.1953MB

第四次

START RequestId: 6faf934b-3787-11ea-8026-52540029942f

Event RequestId: 6faf934b-3787-11ea-8026-52540029942f

222222222


END RequestId: 6faf934b-3787-11ea-8026-52540029942f

Report RequestId: 6faf934b-3787-11ea-8026-52540029942f Duration:1ms Memory:128MB MaxMemoryUsed:27.1953MB

發(fā)現(xiàn)了什么?我在函數(shù)外側(cè)寫的print("11111111")實際上只出現(xiàn)了一次,也就是說他只運行了一次,而函數(shù)內(nèi)的print("222222222")則是出現(xiàn)了多次,確切來說是每次都會出現(xiàn),函數(shù)在創(chuàng)建的時候,會讓我們寫一個執(zhí)行方法,例如index.main_handler,就是說默認(rèn)的入口文件就是index.py下的main_handler方法。通過我們剛才的這個小實驗,是不是可以認(rèn)為,云函數(shù)實際上是隨著機器或者容器啟動同時啟動了一個進(jìn)程(這個時候會走一次外圍的一些代碼邏輯),然后當(dāng)函數(shù)執(zhí)行的時候,會走我們指定的方法,當(dāng)函數(shù)執(zhí)行完,這個容器并不會被馬上銷毀,而是進(jìn)入銷毀的倒計時,這個時候如果有請求來了,那么很可能復(fù)用這個容器,此時就沒有容器啟動的說法,會直接執(zhí)行我們的方法。

按照這個邏輯,是不是我們的函數(shù),如果要在我們的方法之外,初始化數(shù)據(jù)庫,就可以保證盡可能少的數(shù)據(jù)庫連接建立,而滿足更多的請求呢?換句話說,是不是和容器復(fù)用類似,我們就可以復(fù)用數(shù)據(jù)庫的連接了?

所以,我這里可以可以這樣寫我的整個代碼(login為例)

# -*- coding: utf8 -*-

import os
import pymysql
import json

connection = pymysql.connect(host=os.environ.get('mysql_host'),
                             user="root",
                             password=os.environ.get('mysql_password'),
                             port=int(62580),
                             db="mini_album",
                             charset='utf8',
                             cursorclass=pymysql.cursors.DictCursor,
                             autocommit=1)

def getUserInfor(connection, wecaht):
    try:
        connection.ping(reconnect=True)
        cursor = connection.cursor()
        search_stmt = (
            "SELECT * FROM `users` WHERE `wechat`=%s"
        )
        data = (wecaht)
        cursor.execute(search_stmt, data)
        cursor.close()
        result = cursor.fetchall()
        return len(result)
    except Exception as e:
        print("getUserInfor", e)
        try:
            cursor.close()
        except:
            pass
        return False

def addUseerInfor(connection, wecaht, nickname, remark):
    try:
        connection.ping(reconnect=True)
        cursor = connection.cursor()
        insert_stmt = (
            "INSERT INTO users(wechat,nickname,remark) "
            "VALUES (%s,%s,%s)"
        )
        data = (wecaht, nickname, remark)
        cursor.execute(insert_stmt, data)
        cursor.close()
        connection.close()
        return True
    except Exception as e:
        print(e)
        try:
            cursor.close()
        except:
            pass
        return False


def main_handler(event, context):
    print(event)
    body = json.loads(event['body'])
    wecaht = body['wechat']
    nickname = body['nickname']
    remark = str(body['remark'])

    if getUserInfor(connection, wecaht) == 0:
        if addUseerInfor(connection, wecaht, nickname, remark):
            result = True
        else:
            result = False
    else:
        result = True

    return {
        "result": result
    }

是的,基本需求滿足了,但是代碼很難看,很惡心啊。

  • 這個函數(shù),我要作為小程序的一個接口,那么就要接APIGW,那么我應(yīng)該怎么賴在本地測試呢?難不成每次都發(fā)到線上配置APIGW觸發(fā)器才能測試,我的天,太惡心了吧!
  • 這個函數(shù)需要數(shù)據(jù)庫的連接,需要獲取用戶的信息等,難道別的函數(shù)不需要么?如果需要也要每個函數(shù)都要重復(fù)寫這部分代碼?或者說,代碼的復(fù)用應(yīng)該如何處理呢?是否可以提取公共組件呢?

所以,我這里將這個函數(shù),規(guī)范化和完整化:

# -*- coding: utf8 -*-

import json

try:
    import returnCommon
    from mysqlCommon import mysqlCommon
except:
    import common.testCommon

    common.testCommon.setEnv()

    import common.returnCommon as returnCommon
    from common.mysqlCommon import mysqlCommon


mysql = mysqlCommon()


def main_handler(event, context):
    try:
        print(event)

        body = json.loads(event['body'])

        wecaht = body['wechat']
        nickname = body['nickname']
        remark = str(body['remark'])

        if not wecaht:
            return returnCommon.return_msg(True, "請使用微信小程序登陸本頁面。")

        if not mysql.getUserInfor(wecaht):
            if not nickname:
                return returnCommon.return_msg(True, "參數(shù)異常,請重試。")
            if mysql.addUserInfor(wecaht, nickname, remark):
                return returnCommon.return_msg(False, "注冊成功")
            return returnCommon.return_msg(True, "注冊失敗,請重試。")
        return returnCommon.return_msg(False, "登錄成功")
    except Exception as e:
        print(e)
    return returnCommon.return_msg(True, "用戶信息異常,請聯(lián)系管理員處理")

def test():
    event = {
        "requestContext": {
            "serviceId": "service-f94sy04v",
            "path": "/test/{path}",
            "httpMethod": "POST",
            "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
            "identity": {
                "secretId": "abdcdxxxxxxxsdfs"
            },
            "sourceIp": "14.17.22.34",
            "stage": "release"
        },
        "headers": {
            "Accept-Language": "en-US,en,cn",
            "Accept": "text/html,application/xml,application/json",
            "Host": "service-3ei3tii4-251000691.ap-guangzhou.apigateway.myqloud.com",
            "User-Agent": "User Agent String"
        },
        "body": json.dumps({
            "wechat": "12345",
            "nickname": "test",
            "remark": "",
        }),
        "pathParameters": {
            "path": "value"
        },
        "queryStringParameters": {
            "foo": "bar"
        },
        "headerParameters": {
            "Refer": "10.0.2.14"
        },
        "stageVariables": {
            "stage": "release"
        },
        "path": "/test/value",
        "queryString": {
            "foo": "bar",
            "bob": "alice"
        },
        "httpMethod": "POST"
    }
    print(main_handler(event, None))


if __name__ == "__main__":
    test()

數(shù)據(jù)庫等一些公共組件,統(tǒng)一放在common目錄下,例如mysqlCommon.py(部分):

# -*- coding: utf8 -*-

import os
import random
import pymysql
import datetime

try:
    import cosClient
except:
    import common.cosClient as cosClient


class mysqlCommon:
    def __init__(self):
        self.getConnection({
            "host": os.environ.get('mysql_host'),
            "user": os.environ.get('mysql_user'),
            "port": int(os.environ.get('mysql_port')),
            "db": os.environ.get('mysql_db'),
            "password": os.environ.get('mysql_password')
        })

    def getConnection(self, conf):
        self.connection = pymysql.connect(host=conf['host'],
                                          user=conf['user'],
                                          password=conf['password'],
                                          port=int(conf['port']),
                                          db=conf['db'],
                                          charset='utf8',
                                          cursorclass=pymysql.cursors.DictCursor,
                                          autocommit=1)

    def doAction(self, stmt, data):
        try:
            self.connection.ping(reconnect=True)
            cursor = self.connection.cursor()
            cursor.execute(stmt, data)
            result = cursor
            cursor.close()
            return result
        except Exception as e:
            print(e)
            try:
                cursor.close()
            except:
                pass
            return False

    def addUserInfor(self, wecaht, nickname, remark):
        insert_stmt = (
            "INSERT INTO users(wechat, nickname, remark) "
            "VALUES (%s,%s,%s)"
        )
        data = (wecaht, nickname, remark)
        result = self.doAction(insert_stmt, data)
        return False if result == False else True
       

這樣做的好處是:

  • 我將數(shù)據(jù)庫提取出一個公共組件,便于維護(hù)
  • 在login函數(shù)中,我根據(jù)不同的時期(本地開發(fā)和線上),我導(dǎo)入不同的模塊:
try:
    import cosClient
except:
    import common.cosClient as cosClient

這樣會更加便利,同時模擬網(wǎng)關(guān),做一個測試方法:

def test():
    event = {
        "requestContext": {
            "serviceId": "service-f94sy04v",
            "path": "/test/{path}",
            "httpMethod": "POST",
            "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
            "identity": {
                "secretId": "abdcdxxxxxxxsdfs"
            },
            "sourceIp": "14.17.22.34",
            "stage": "release"
        },
        "headers": {
            "Accept-Language": "en-US,en,cn",
            "Accept": "text/html,application/xml,application/json",
            "Host": "service-3ei3tii4-251000691.ap-guangzhou.apigateway.myqloud.com",
            "User-Agent": "User Agent String"
        },
        "body": json.dumps({
            "wechat": "12345",
            "nickname": "test",
            "remark": "",
        }),
        "pathParameters": {
            "path": "value"
        },
        "queryStringParameters": {
            "foo": "bar"
        },
        "headerParameters": {
            "Refer": "10.0.2.14"
        },
        "stageVariables": {
            "stage": "release"
        },
        "path": "/test/value",
        "queryString": {
            "foo": "bar",
            "bob": "alice"
        },
        "httpMethod": "POST"
    }
    print(main_handler(event, None))

增加本地測試時,指定test()方法:

if __name__ == "__main__":
    test()

這樣,線上觸發(fā)時,會默認(rèn)執(zhí)行main_handler, 而本地執(zhí)行,則會通過test走入main_handler,我們可以邊開發(fā),邊測試,弄好了再部署到線上。

同時,我們線上獲取配置信息是通過獲取環(huán)境變量,那么我們本地呢?

我們可以在本地執(zhí)行的時候,先進(jìn)行這個操作:

# -*- coding: utf8 -*-

import yaml
import os


def setEnv():
    file = open("/Users/dfounderliu/Documents/code/AIAlbum/serverless.yaml", 'r', encoding="utf-8")
    file_data = file.read()
    file.close()

    data = yaml.load(file_data)
    for eveKey, eveValue in data['Conf']['inputs'].items():
        print(eveKey, eveValue)
        os.environ[eveKey] = str(eveValue)

這樣,我們這個文件就非常完美的,可以線上直接用,也可以本地直接用了!

那么我們的Yaml怎么寫?

image

是的,這樣我們就可以很簡單輕松加愉快的將我們的公共組件庫,在部署函數(shù)的時候,引入到項目中。

本地長這樣:

image

對于這個項目,完美解決本地調(diào)試,線上運行的全兼容問題。

通過上面簡單的實驗和分析,我們知道了如何制作公共組件庫,如何定義Components的全局變量,如何本地調(diào)試和線上觸發(fā)二者兼得,以及在什么地方初始化數(shù)據(jù)庫"性價比較高",完成了上面的所有部分,就是我們進(jìn)行各個子功能函數(shù)編寫的工作了,基本都是數(shù)據(jù)庫的增刪改查。此處不再一一描述。編寫完函數(shù)之后,可以編寫我們的小程序端。

項目開發(fā)

數(shù)據(jù)庫建立

image

數(shù)據(jù)庫部分主要對相關(guān)的表和表之間的關(guān)系進(jìn)行建立。
首先需要創(chuàng)建項目所必須的表:


CREATE DATABASE `album`;
CREATE TABLE `album`.`tags` ( `tid` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(255) NOT NULL , `remark` TEXT NULL , PRIMARY KEY (`tid`)) ENGINE = InnoDB;
CREATE TABLE `album`.`category` ( `cid` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(255) NOT NULL , `sorted` INT NOT NULL DEFAULT '1' , `user` INT NOT NULL , `remark` TEXT NULL , `publish` DATE NOT NULL , `area` VARCHAR(255) NULL , PRIMARY KEY (`cid`)) ENGINE = InnoDB;
CREATE TABLE `album`.`users` ( `uid` INT NOT NULL AUTO_INCREMENT , `nickname` TEXT NOT NULL , `wechat` VARCHAR(255) NOT NULL , `remark` TEXT NULL , PRIMARY KEY (`uid`)) ENGINE = InnoDB;
CREATE TABLE `album`.`photo` ( `pid` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(255) NOT NULL , `small` VARCHAR(255) NOT NULL , `large` VARCHAR(255) NOT NULL , `category` INT NOT NULL , `tags` VARCHAR(255) NULL , `remark` TEXT NULL , `creattime` DATE NOT NULL , `creatarea` VARCHAR(255) NOT NULL , `user` INT NOT NULL ,  PRIMARY KEY (`pid`)) ENGINE = InnoDB;
CREATE TABLE `album`.`photo_tags` ( `ptid` INT NOT NULL AUTO_INCREMENT , `tag` INT NOT NULL , `photo` INT NOT NULL , `remark` INT NULL , PRIMARY KEY (`ptid`)) ENGINE = InnoDB;

創(chuàng)建之后,逐步添加表之間的關(guān)系以及部分限制條件:


ALTER TABLE `photo_tags` ADD CONSTRAINT `photo_tags_tags_alter` FOREIGN KEY (`tag`) REFERENCES `tags`(`tid`) ON DELETE CASCADE ON UPDATE RESTRICT; 
ALTER TABLE `photo_tags` ADD CONSTRAINT `photo_tags_photo_alter` FOREIGN KEY (`photo`) REFERENCES `photo`(`pid`) ON DELETE CASCADE ON UPDATE RESTRICT;
ALTER TABLE `photo` ADD CONSTRAINT `photo_category_alter` FOREIGN KEY (`category`) REFERENCES `category`(`cid`) ON DELETE CASCADE ON UPDATE RESTRICT;
ALTER TABLE `photo` ADD CONSTRAINT `photo_user_alter` FOREIGN KEY (`user`) REFERENCES `users`(`uid`) ON DELETE CASCADE ON UPDATE RESTRICT;
ALTER TABLE `category` ADD CONSTRAINT `category_user_alter` FOREIGN KEY (`user`) REFERENCES `users`(`uid`) ON DELETE CASCADE ON UPDATE RESTRICT;
ALTER TABLE `tags` ADD unique(`name`);

讓Code飛起來

  • 在使用之前您需要有一個騰訊云的賬號,并且開通了COS、COS、APIGW以及CDB等相關(guān)產(chǎn)品權(quán)限;
  • 將項目clone到本地,配置自己的密鑰信息、數(shù)據(jù)庫信息。配置文件在cloudFunction目錄下的serverless.yaml中:
# 函數(shù)們的整體配置信息
Conf:
  component: "serverless-global"
  inputs:
    region: ap-shanghai
    runtime: Python3.6
    handler: index.main_handler
    include_common: ./common
    mysql_host: gz-c************************.com
    mysql_user: root
    mysql_password: S************************!
    mysql_port: 6************************0
    mysql_db: album
    mini_program_app_id: asdsa************************dddd
    mini_program_app_secret: fd340c4************************8744ee
    tencent_secret_id: AKID1y************************l1q0kK
    tencent_secret_key: cCoJ************************FZj5Oa
    tencent_appid: 1256773370
    cos_bucket: 'album-1256773370'
    domain: album.0duzahn.com

由于我目前使用的是Serverless Components,沒有全局變量等,所以在此處增加了全局變量組件,在這里設(shè)置好全局變量,在之后的Components中可以直接引用,例如:

# 創(chuàng)建存儲桶
CosBucket:
  component: '@serverless/tencent-website'
  inputs:
    code:
      src: ./cos
    region:  ${Conf.region}
    bucketName: ${Conf.cos_bucket}
  • 安裝必備工具,例如必須要安裝Serverless Framework(可以參考:https://cloud.tencent.com/document/product/1154/39005), 同樣由于本項目后臺開發(fā)語言是Python,您也需要一些Python的開發(fā)工具以及包管理工具,以及小程序云開發(fā)的IDE;
  • 在部分文件夾下安裝相對應(yīng)的依賴:
    • cloudFunction/album/prdiction需要安裝Pillow, opencv,tensorflow,jieba
    • cloudFunction/album/getPhotoSearch需要安裝gensim,jieba以及collections
    • cloudFunction/album/compression需要安裝Pillow
      注意,在安裝的時候一定要用CentOS操作系統(tǒng),并且Python要3.6版本,如果沒相對應(yīng)系統(tǒng),可以在這里打包對應(yīng)的依賴:http://serverless.0duzhan.com/app/scf_python_package_download/
  • 將項目部署到云端,只需要通過指令serverless --debug即可:
DEBUG ─ Resolving the template's static variables.
  DEBUG ─ Collecting components from the template.
  DEBUG ─ Downloading any NPM components found in the template.
  DEBUG ─ Analyzing the template's components dependencies.
  DEBUG ─ Creating the template's components graph.
  DEBUG ─ Syncing template state.
  DEBUG ─ Executing the template's components graph.
  DEBUG ─ Starting API-Gateway deployment with name APIService in the ap-shanghai region
 
    ... ...
 
  DEBUG ─ Updating configure... 
  DEBUG ─ Created function Album_Get_Photo_Search successful
  DEBUG ─ Setting tags for function Album_Get_Photo_Search
  DEBUG ─ Creating trigger for function Album_Get_Photo_Search
  DEBUG ─ Deployed function Album_Get_Photo_Search successful
  DEBUG ─ Uploaded package successful /Users/dfounderliu/Documents/code/AIAlbum/.serverless/Album_Prediction.zip
  DEBUG ─ Creating function Album_Prediction
  DEBUG ─ Updating code... 
  DEBUG ─ Updating configure... 
  DEBUG ─ Created function Album_Prediction successful
  DEBUG ─ Setting tags for function Album_Prediction
  DEBUG ─ Creating trigger for function Album_Prediction
  DEBUG ─ Trigger timer: timer not changed
  DEBUG ─ Deployed function Album_Prediction successful

  Conf: 
    region:                  ap-shanghai
      
      ... ...
      
      - 
        path:   /photo/delete
        method: ANY
        apiId:  api-g9u6r9wq
      - 
        path:   /album/delete
        method: ANY
        apiId:  api-b4c4xrq8
      - 
        path:   /album/add
        method: ANY
        apiId:  api-ml6q5koy

  156s ? APIService ? done

例如我的這個過程,只用了156s部署了所有函數(shù),然后打開小程序的id帶入miniProgram目錄,并且填寫自己的appid在文件project.config.json的第17行,同時也要配置自己項目的基礎(chǔ)目錄,就是API網(wǎng)關(guān)給我們返回的地址,寫在app.js的第10行,此時項目就可以運行起來了。
當(dāng)然,考慮到部分小伙伴可能比較喜歡方便,所以我這里也提供了后臺的壓縮包: https://album-1256773370.cos.ap-shanghai.myqcloud.com/others/AIAlbum.zip


image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容