最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Python實(shí)現(xiàn)Flutter項(xiàng)目的自動(dòng)化構(gòu)建與發(fā)布

 更新時(shí)間:2026年02月03日 08:44:17   作者:明君87997  
本文介紹了作者為解決移動(dòng)端Flutter應(yīng)用開(kāi)發(fā)中構(gòu)建、上傳和通知的繁瑣問(wèn)題,使用Python編寫(xiě)的一套自動(dòng)化腳本,該腳本實(shí)現(xiàn)了自動(dòng)構(gòu)建、上傳到蒲公英測(cè)試平臺(tái)、發(fā)送郵件通知等功能,顯著提高了開(kāi)發(fā)效率,需要的朋友可以參考下

前言

作為公司唯一的移動(dòng)端開(kāi)發(fā),我需要同時(shí)負(fù)責(zé) 5 個(gè) Flutter App 的開(kāi)發(fā)和維護(hù)工作。每個(gè)應(yīng)用都需要支持 iOS 和 Android 雙平臺(tái),這意味著每次發(fā)版我可能要進(jìn)行多達(dá) 10 次的打包操作。如果每次都手動(dòng)執(zhí)行構(gòu)建、上傳、通知這些重復(fù)性工作,不僅耗時(shí)巨大,還極易出錯(cuò)。

為了從繁瑣的重復(fù)勞動(dòng)中解放出來(lái),把更多精力投入到真正有價(jià)值的開(kāi)發(fā)工作中,我使用 Python 編寫(xiě)了一套自動(dòng)化腳本,實(shí)現(xiàn)了一鍵完成構(gòu)建、上傳和通知的完整流程。

本文將分享我在這個(gè)過(guò)程中的實(shí)踐經(jīng)驗(yàn)和代碼實(shí)現(xiàn)。

項(xiàng)目背景

公司目前有 5 個(gè)移動(dòng)端應(yīng)用在同時(shí)運(yùn)營(yíng),而移動(dòng)端開(kāi)發(fā)只有我一個(gè)人。每個(gè)應(yīng)用都是基于 Flutter 開(kāi)發(fā)的跨平臺(tái)應(yīng)用,需要同時(shí)支持 iOS 和 Android 平臺(tái)。在日常開(kāi)發(fā)中,存在以下痛點(diǎn):

  1. 項(xiàng)目多、人手少:5 個(gè) App × 2 個(gè)平臺(tái) = 10 個(gè)構(gòu)建任務(wù),一個(gè)人根本忙不過(guò)來(lái)
  2. 構(gòu)建流程繁瑣:每次打包都需要手動(dòng)執(zhí)行多個(gè)命令,切換項(xiàng)目、切換環(huán)境配置
  3. 上傳步驟重復(fù):構(gòu)建完成后需要手動(dòng)上傳到測(cè)試平臺(tái)(蒲公英),操作機(jī)械且耗時(shí)
  4. 通知不及時(shí):需要手動(dòng)通知測(cè)試人員新版本已就緒,容易遺漏
  5. iOS 構(gòu)建環(huán)境問(wèn)題:CocoaPods 緩存問(wèn)題經(jīng)常導(dǎo)致構(gòu)建失敗,排查費(fèi)時(shí)費(fèi)力

面對(duì)這樣的工作強(qiáng)度,自動(dòng)化不再是"錦上添花",而是"剛需"。

技術(shù)方案

我設(shè)計(jì)了以下幾個(gè) Python 腳本來(lái)解決這些問(wèn)題:

python/
├── build_app.py          # 主構(gòu)建腳本(iOS + Android)
├── build_android_app.py  # Android 單獨(dú)構(gòu)建腳本
├── clean_ios_build.py    # iOS 構(gòu)建環(huán)境清理
├── force_clean_ios.py    # 強(qiáng)制清理腳本
├── bulk_email.py         # 群發(fā)郵件工具類
├── send_email.py         # 單封郵件發(fā)送
└── test_email_auth.py    # 郵箱授權(quán)測(cè)試

核心實(shí)現(xiàn)

1. 自動(dòng)構(gòu)建腳本

構(gòu)建腳本的核心功能是自動(dòng)執(zhí)行 Flutter 構(gòu)建命令,并支持不同環(huán)境(開(kāi)發(fā)/生產(chǎn))的配置。

#!/usr/local/bin/python3

import os
import subprocess

# 獲取當(dāng)前腳本所在目錄
script_dir = os.path.dirname(os.path.abspath(__file__))
# Flutter項(xiàng)目根目錄(python文件夾在項(xiàng)目根目錄下)
flutter_root = os.path.dirname(script_dir)

# 獲取用戶輸入的環(huán)境
env = input("請(qǐng)輸入環(huán)境(dev/prod): ")

# 檢查環(huán)境配置文件是否存在
env_file = os.path.join(flutter_root, f"{env}.json")
if not os.path.exists(env_file):
    print(f"錯(cuò)誤: 環(huán)境配置文件 {env}.json 不存在")
    exit(1)

# 切換到Flutter項(xiàng)目根目錄
os.chdir(flutter_root)

# 構(gòu)建Android應(yīng)用
def build_android(env):
    print("正在構(gòu)建Android應(yīng)用...")
    env_text = '生產(chǎn)' if env == 'prod' else '開(kāi)發(fā)'
    print(f"構(gòu)建版本: {env_text}環(huán)境...")
    
    # 構(gòu)建命令,支持代碼混淆
    build_command = f'fvm flutter build apk --release --dart-define-from-file={env}.json --obfuscate --split-debug-info=./build/debug_info'
    
    try:
        process = subprocess.run(
            build_command.split(), 
            stdout=subprocess.PIPE, 
            stderr=subprocess.PIPE, 
            text=True
        )
        print("構(gòu)建輸出:")
        print(process.stdout)
        print("Android構(gòu)建成功!")
        print("APK文件路徑: build/app/outputs/flutter-apk/app-release.apk")
        return True
    except subprocess.CalledProcessError as e:
        print(f"Android構(gòu)建失敗: {e}")
        return False

# 構(gòu)建iOS應(yīng)用
def build_ios(env, upload_to_appstore=False):
    print("正在構(gòu)建iOS應(yīng)用...")
    env_text = '生產(chǎn)' if env == 'prod' else '開(kāi)發(fā)'
    
    # 根據(jù)是否上傳App Store選擇導(dǎo)出方法
    export_method = "app-store" if upload_to_appstore else "development"
    build_command = f"fvm flutter build ipa --release --export-method {export_method} --dart-define-from-file={env}.json --obfuscate --split-debug-info=./build/debug_info"
    
    try:
        process = subprocess.run(
            build_command.split(), 
            stdout=subprocess.PIPE, 
            stderr=subprocess.PIPE, 
            text=True
        )
        print("構(gòu)建輸出:")
        print(process.stdout)
        print("iOS構(gòu)建成功!")
        return True
    except subprocess.CalledProcessError as e:
        print(f"iOS構(gòu)建失敗: {e}")
        return False

2. 自動(dòng)上傳到蒲公英

構(gòu)建完成后,自動(dòng)將安裝包上傳到蒲公英測(cè)試平臺(tái):

import requests

def upload_to_pgyer(env, ipa_path, platform):
    """上傳到蒲公英測(cè)試平臺(tái)"""
    print(f"正在上傳到蒲公英...")
    print(f"文件路徑: {ipa_path}")
    
    # 從配置文件或環(huán)境變量讀取 API Key
    api_key = os.environ.get('PGYER_API_KEY', 'your_api_key')
    user_key = os.environ.get('PGYER_USER_KEY', 'your_user_key')
    
    files = {"file": open(ipa_path, "rb")}
    headers = {"enctype": "multipart/form-data"}
    
    platform_text = "android" if platform == "android" else "ios"
    payload = {
        "uKey": user_key,
        "_api_key": api_key,
        "installType": 1,
        "updateDescription": f"{platform_text}自動(dòng)化打包"
    }
    
    try:
        response = requests.post(
            "https://www.pgyer.com/apiv2/app/upload", 
            data=payload, 
            files=files, 
            headers=headers
        )
        result = response.json()
        
        # 獲取構(gòu)建信息
        qr_code_url = result["data"]["buildQRCodeURL"]
        version = result["data"]["buildVersion"]
        version_no = result["data"]["buildVersionNo"]
        build_name = result["data"]["buildName"]
        
        print(f"上傳成功!")
        print(f"二維碼地址: {qr_code_url}")
        print(f"版本: {version} ({version_no})")
        
        return {
            "qr_code_url": qr_code_url,
            "version": version,
            "version_no": version_no,
            "build_name": build_name
        }
    except Exception as e:
        print(f"上傳失敗: {e}")
        return None

3. 群發(fā)郵件通知

構(gòu)建并上傳成功后,自動(dòng)發(fā)送郵件通知團(tuán)隊(duì)成員:

#!/usr/local/bin/python3

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import os
import time
from typing import List, Dict, Optional

class BulkEmailSender:
    """群發(fā)郵件發(fā)送器"""
    
    def __init__(self, smtp_server: str, smtp_port: int, 
                 sender_email: str, sender_password: str):
        """
        初始化群發(fā)郵件發(fā)送器
        
        Args:
            smtp_server: SMTP服務(wù)器地址
            smtp_port: SMTP端口
            sender_email: 發(fā)送者郵箱
            sender_password: 發(fā)送者郵箱密碼或授權(quán)碼
        """
        self.smtp_server = smtp_server
        self.smtp_port = smtp_port
        self.sender_email = sender_email
        self.sender_password = sender_password
    
    def _create_connection(self):
        """創(chuàng)建SMTP連接"""
        try:
            if self.smtp_port == 465:
                # 使用SSL連接(465端口)
                server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port)
            else:
                # 使用STARTTLS連接(587/25端口)
                server = smtplib.SMTP(self.smtp_server, self.smtp_port)
                server.starttls()
            
            server.login(self.sender_email, self.sender_password)
            return server
        except Exception as e:
            print(f"連接失敗: {e}")
            return None
    
    def send_bulk_individual(self, recipients: List[str], subject: str, 
                            body: str, html_body: Optional[str] = None,
                            attachment_path: Optional[str] = None,
                            delay: float = 1.0) -> Dict[str, bool]:
        """
        逐個(gè)發(fā)送郵件(隱私保護(hù)最好,每個(gè)人只能看到自己的郵箱)
        
        Args:
            recipients: 收件人列表
            subject: 郵件主題
            body: 郵件內(nèi)容(純文本)
            html_body: HTML郵件內(nèi)容(可選)
            attachment_path: 附件路徑(可選)
            delay: 發(fā)送間隔(秒),避免被服務(wù)器限制
        """
        results = {}
        server = self._create_connection()
        
        if not server:
            return {email: False for email in recipients}
        
        try:
            for i, recipient in enumerate(recipients):
                try:
                    print(f"發(fā)送郵件 {i+1}/{len(recipients)} 到: {recipient}")
                    
                    # 創(chuàng)建郵件
                    if html_body:
                        msg = MIMEMultipart('alternative')
                        msg.attach(MIMEText(body, 'plain', 'utf-8'))
                        msg.attach(MIMEText(html_body, 'html', 'utf-8'))
                    else:
                        msg = MIMEMultipart()
                        msg.attach(MIMEText(body, 'plain', 'utf-8'))
                    
                    msg['From'] = self.sender_email
                    msg['To'] = recipient
                    msg['Subject'] = subject
                    
                    # 添加附件
                    if attachment_path and os.path.exists(attachment_path):
                        self._add_attachment(msg, attachment_path)
                    
                    # 發(fā)送郵件
                    server.sendmail(self.sender_email, [recipient], msg.as_string())
                    results[recipient] = True
                    print(f"? 發(fā)送成功: {recipient}")
                    
                    # 延遲避免被限制
                    if i < len(recipients) - 1:
                        time.sleep(delay)
                        
                except Exception as e:
                    results[recipient] = False
                    print(f"? 發(fā)送失敗 {recipient}: {e}")
                    
        finally:
            server.quit()
            
        return results
    
    def send_bulk_bcc(self, recipients: List[str], subject: str, body: str,
                     html_body: Optional[str] = None, batch_size: int = 50) -> bool:
        """
        使用BCC批量發(fā)送(隱私保護(hù),收件人看不到其他人)
        適合大批量發(fā)送通知郵件
        """
        server = self._create_connection()
        if not server:
            return False
        
        try:
            # 分批發(fā)送,避免單次發(fā)送太多
            for i in range(0, len(recipients), batch_size):
                batch = recipients[i:i + batch_size]
                print(f"發(fā)送批次 {i//batch_size + 1}: {len(batch)} 個(gè)收件人")
                
                if html_body:
                    msg = MIMEMultipart('alternative')
                    msg.attach(MIMEText(body, 'plain', 'utf-8'))
                    msg.attach(MIMEText(html_body, 'html', 'utf-8'))
                else:
                    msg = MIMEMultipart()
                    msg.attach(MIMEText(body, 'plain', 'utf-8'))
                
                msg['From'] = self.sender_email
                msg['To'] = self.sender_email  # 顯示發(fā)送者自己
                msg['Bcc'] = ', '.join(batch)   # 密送給所有收件人
                msg['Subject'] = subject
                
                all_recipients = [self.sender_email] + batch
                server.sendmail(self.sender_email, all_recipients, msg.as_string())
                print(f"? 批次發(fā)送成功: {len(batch)} 個(gè)收件人")
                
                if i + batch_size < len(recipients):
                    time.sleep(2)
                    
            return True
            
        except Exception as e:
            print(f"? BCC群發(fā)失敗: {e}")
            return False
        finally:
            server.quit()
    
    def _add_attachment(self, msg: MIMEMultipart, attachment_path: str):
        """添加附件到郵件"""
        with open(attachment_path, "rb") as attachment:
            part = MIMEBase('application', 'octet-stream')
            part.set_payload(attachment.read())
        
        encoders.encode_base64(part)
        part.add_header(
            'Content-Disposition',
            f'attachment; filename= {os.path.basename(attachment_path)}'
        )
        msg.attach(part)

4. 發(fā)送構(gòu)建通知郵件

將構(gòu)建信息通過(guò) HTML 郵件發(fā)送給團(tuán)隊(duì):

def send_build_notification(build_info, env, platform, test_content=""):
    """發(fā)送構(gòu)建通知郵件"""
    
    env_text = '生產(chǎn)' if env == 'prod' else '開(kāi)發(fā)'
    platform_text = "Android" if platform == "android" else "iOS"
    
    # 構(gòu)建HTML郵件內(nèi)容
    html_body = f"""
    <html>
        <body>
            <h2>項(xiàng)目構(gòu)建通知</h2>
            <p>構(gòu)建狀態(tài): <span style="color: green;"><b>成功</b></span></p>
            <ul>
                <li>構(gòu)建名稱: {build_info['build_name']}</li>
                <li>平臺(tái): {platform_text}</li>
                <li>環(huán)境: {env_text}</li>
                <li>版本: {build_info['version']}</li>
                <li>版本號(hào): {build_info['version_no']}</li>
                <li>測(cè)試內(nèi)容: {test_content}</li>
            </ul>
            <img src="{build_info['qr_code_url']}" alt="下載二維碼">
            <p>請(qǐng)掃描二維碼下載安裝測(cè)試。</p>
        </body>
    </html>
    """
    
    # 從環(huán)境變量讀取郵件配置
    smtp_server = os.environ.get('SMTP_SERVER', 'smtp.exmail.qq.com')
    smtp_port = int(os.environ.get('SMTP_PORT', '587'))
    sender_email = os.environ.get('SENDER_EMAIL')
    sender_password = os.environ.get('SENDER_PASSWORD')
    
    bulk_sender = BulkEmailSender(
        smtp_server=smtp_server,
        smtp_port=smtp_port,
        sender_email=sender_email,
        sender_password=sender_password
    )
    
    # 收件人列表(從配置文件讀?。?
    recipients = load_recipients_from_config()
    
    subject = f"構(gòu)建通知: {build_info['build_name']} - {platform_text} - {env_text}環(huán)境"
    
    results = bulk_sender.send_bulk_individual(
        recipients=recipients,
        subject=subject,
        body=f'{platform_text}打包通知',
        html_body=html_body,
        delay=0.5
    )
    
    return results

5. iOS 構(gòu)建環(huán)境清理腳本

在 iOS 開(kāi)發(fā)中,經(jīng)常會(huì)遇到 CocoaPods 緩存導(dǎo)致的構(gòu)建問(wèn)題。這個(gè)腳本可以徹底清理構(gòu)建環(huán)境:

#!/usr/bin/env python3
"""
iOS 構(gòu)建環(huán)境清理腳本
用于解決 Firebase Crashlytics 模塊化頭文件等常見(jiàn)問(wèn)題
"""

import os
import subprocess
import shutil

def run_command(command, description):
    """執(zhí)行命令并打印結(jié)果"""
    print(f"\n{description}...")
    print(f"執(zhí)行命令: {command}")
    
    try:
        result = subprocess.run(command, shell=True, capture_output=True, text=True)
        if result.stdout:
            print("輸出:", result.stdout)
        if result.returncode == 0:
            print(f"? {description} 成功")
        else:
            print(f"? {description} 失敗")
        return result.returncode == 0
    except Exception as e:
        print(f"? {description} 異常: {e}")
        return False

def force_clean_ios():
    """強(qiáng)制清理 iOS 構(gòu)建環(huán)境"""
    print("?? 開(kāi)始強(qiáng)制清理 iOS 構(gòu)建環(huán)境...")
    
    # 獲取項(xiàng)目根目錄
    project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    ios_dir = os.path.join(project_root, "ios")
    
    if not os.path.exists(ios_dir):
        print(f"? iOS 目錄不存在: {ios_dir}")
        return False
    
    os.chdir(project_root)
    print(f"?? 當(dāng)前工作目錄: {os.getcwd()}")
    
    # 1. 清理 Flutter 緩存
    run_command("fvm flutter clean", "清理 Flutter 構(gòu)建緩存")
    
    # 2. 刪除 pubspec.lock
    pubspec_lock = os.path.join(project_root, "pubspec.lock")
    if os.path.exists(pubspec_lock):
        print(f"??? 刪除 pubspec.lock")
        os.remove(pubspec_lock)
    
    # 3. 刪除 .dart_tool 目錄
    dart_tool_dir = os.path.join(project_root, ".dart_tool")
    if os.path.exists(dart_tool_dir):
        print(f"??? 刪除 .dart_tool 目錄")
        shutil.rmtree(dart_tool_dir)
    
    # 4. 刪除 iOS 構(gòu)建目錄
    for dir_name in ["build", "Pods", ".symlinks"]:
        dir_path = os.path.join(ios_dir, dir_name)
        if os.path.exists(dir_path):
            print(f"??? 刪除 {dir_name} 目錄")
            shutil.rmtree(dir_path)
    
    # 5. 刪除 Podfile.lock
    podfile_lock = os.path.join(ios_dir, "Podfile.lock")
    if os.path.exists(podfile_lock):
        print(f"??? 刪除 Podfile.lock")
        os.remove(podfile_lock)
    
    # 6. 清理 CocoaPods 緩存
    run_command("pod cache clean --all", "清理 CocoaPods 緩存")
    
    # 7. 重新獲取 Flutter 依賴
    run_command("fvm flutter pub get", "重新獲取 Flutter 依賴")
    
    # 8. 重新安裝 Pods
    os.chdir(ios_dir)
    run_command("pod install --repo-update", "重新安裝 Pods")
    
    print("\n?? 強(qiáng)制清理完成!")
    print("?? 現(xiàn)在可以重新嘗試構(gòu)建 iOS 應(yīng)用了")
    
    return True

if __name__ == "__main__":
    force_clean_ios()

6. 郵箱授權(quán)測(cè)試工具

在配置郵件服務(wù)前,可以使用這個(gè)工具測(cè)試授權(quán)碼是否正確:

#!/usr/local/bin/python3

import smtplib

def test_email_auth(smtp_server, smtp_port, email, auth_code):
    """
    測(cè)試郵箱授權(quán)碼是否正確
    """
    try:
        print(f"正在測(cè)試郵箱: {email}")
        print(f"SMTP服務(wù)器: {smtp_server}:{smtp_port}")
        
        # 連接SMTP服務(wù)器
        server = smtplib.SMTP(smtp_server, smtp_port)
        server.starttls()
        
        # 嘗試登錄
        server.login(email, auth_code)
        server.quit()
        
        print("? 授權(quán)碼驗(yàn)證成功!")
        return True
        
    except smtplib.SMTPAuthenticationError:
        print("? 授權(quán)碼驗(yàn)證失??!請(qǐng)檢查:")
        print("   1. 授權(quán)碼是否正確")
        print("   2. 是否已開(kāi)啟SMTP服務(wù)")
        print("   3. 是否使用了郵箱密碼而非授權(quán)碼")
        return False
        
    except Exception as e:
        print(f"? 連接失敗: {e}")
        return False

if __name__ == "__main__":
    # 常用郵箱SMTP配置
    email_configs = {
        'qq': ('smtp.qq.com', 587),
        '163': ('smtp.163.com', 25),
        'gmail': ('smtp.gmail.com', 587),
        'outlook': ('smtp-mail.outlook.com', 587),
        'wechat': ('smtp.exmail.qq.com', 587)
    }
    
    print("=== 郵箱授權(quán)碼測(cè)試工具 ===\n")
    
    email_type = input("請(qǐng)選擇郵箱類型 (qq/163/gmail/outlook/wechat): ").lower()
    
    if email_type not in email_configs:
        print("不支持的郵箱類型")
        exit(1)
    
    email = input("請(qǐng)輸入郵箱地址: ")
    auth_code = input("請(qǐng)輸入授權(quán)碼: ")
    
    smtp_server, smtp_port = email_configs[email_type]
    test_email_auth(smtp_server, smtp_port, email, auth_code)

使用方式

1. 環(huán)境準(zhǔn)備

首先確保安裝了必要的 Python 依賴:

pip install requests

2. 配置敏感信息

建議使用環(huán)境變量或配置文件管理敏感信息,不要硬編碼在腳本中:

# 設(shè)置環(huán)境變量
export PGYER_API_KEY="your_api_key"
export PGYER_USER_KEY="your_user_key"
export SENDER_EMAIL="your_email@example.com"
export SENDER_PASSWORD="your_auth_code"
export SMTP_SERVER="smtp.exmail.qq.com"
export SMTP_PORT="587"

3. 執(zhí)行構(gòu)建

# 進(jìn)入 python 腳本目錄
cd python

# 執(zhí)行構(gòu)建腳本
python3 build_app.py

腳本會(huì)依次提示:

  1. 選擇環(huán)境(dev/prod)
  2. 是否發(fā)送郵件通知
  3. 輸入測(cè)試內(nèi)容
  4. 是否上傳到 App Store(僅生產(chǎn)環(huán)境)

4. 清理 iOS 構(gòu)建環(huán)境

當(dāng)遇到 iOS 構(gòu)建問(wèn)題時(shí),執(zhí)行:

python3 force_clean_ios.py

最佳實(shí)踐

1. 敏感信息管理

  • 使用環(huán)境變量存儲(chǔ) API Key、密碼等敏感信息
  • 不要將敏感信息提交到版本控制
  • 可以使用 .env 文件配合 python-dotenv 庫(kù)

2. 錯(cuò)誤處理

  • 每個(gè)關(guān)鍵步驟都添加 try-except 處理
  • 構(gòu)建失敗時(shí)輸出詳細(xì)錯(cuò)誤信息
  • 記錄日志便于問(wèn)題排查

3. 郵件發(fā)送策略

  • 群發(fā)郵件時(shí)添加適當(dāng)延遲,避免被服務(wù)器限制
  • 使用 BCC 方式保護(hù)收件人隱私
  • 分批發(fā)送大量郵件

4. 構(gòu)建優(yōu)化

  • 使用 --obfuscate 參數(shù)進(jìn)行代碼混淆
  • 使用 --split-debug-info 分離調(diào)試信息
  • 根據(jù)環(huán)境使用不同的配置文件

總結(jié)

通過(guò) Python 腳本實(shí)現(xiàn) Flutter 項(xiàng)目的自動(dòng)化構(gòu)建,可以顯著提高開(kāi)發(fā)效率:

  1. 一鍵完成:構(gòu)建、上傳、通知全流程自動(dòng)化
  2. 減少出錯(cuò):避免手動(dòng)操作帶來(lái)的失誤
  3. 節(jié)省時(shí)間:構(gòu)建期間可以專注于其他工作
  4. 規(guī)范流程:統(tǒng)一的構(gòu)建和發(fā)布流程

這套腳本已經(jīng)在我使用了一段時(shí)間,效果良好。希望這篇文章對(duì)有類似需求的開(kāi)發(fā)者有所幫助。

相關(guān)技術(shù)棧:

  • Python 3.x
  • Flutter + FVM
  • 蒲公英測(cè)試平臺(tái)
  • SMTP 郵件服務(wù)

以上就是使用Python實(shí)現(xiàn)Flutter項(xiàng)目的自動(dòng)化構(gòu)建與發(fā)布的詳細(xì)內(nèi)容,更多關(guān)于Python Flutter自動(dòng)化構(gòu)建與發(fā)布的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • pymysql 插入數(shù)據(jù) 轉(zhuǎn)義處理方式

    pymysql 插入數(shù)據(jù) 轉(zhuǎn)義處理方式

    今天小編就為大家分享一篇pymysql 插入數(shù)據(jù) 轉(zhuǎn)義處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-03-03
  • Python中使用Counter進(jìn)行字典創(chuàng)建以及key數(shù)量統(tǒng)計(jì)的方法

    Python中使用Counter進(jìn)行字典創(chuàng)建以及key數(shù)量統(tǒng)計(jì)的方法

    今天小編就為大家分享一篇Python中使用Counter進(jìn)行字典創(chuàng)建以及key數(shù)量統(tǒng)計(jì)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07
  • uv終極技巧之一招精準(zhǔn)指定Python版本(徹底告別版本混亂!)

    uv終極技巧之一招精準(zhǔn)指定Python版本(徹底告別版本混亂!)

    uv是一個(gè)高效的跨平臺(tái)Python版本管理工具,專注于簡(jiǎn)化開(kāi)發(fā)者管理和切換不同Python版本的過(guò)程,旨在簡(jiǎn)化Python開(kāi)發(fā)環(huán)境的配置和管理,這篇文章主要介紹了uv終極技巧之一招精準(zhǔn)指定Python版本的相關(guān)資料,需要的朋友可以參考下
    2025-12-12
  • python 文件下載之?dāng)帱c(diǎn)續(xù)傳的實(shí)現(xiàn)

    python 文件下載之?dāng)帱c(diǎn)續(xù)傳的實(shí)現(xiàn)

    用python進(jìn)行文件下載的時(shí)候,一旦出現(xiàn)網(wǎng)絡(luò)波動(dòng)問(wèn)題,導(dǎo)致文件下載到一半。如果將下載不完全的文件刪掉,那么又需要從頭開(kāi)始,如果連續(xù)網(wǎng)絡(luò)波動(dòng),是不是要頭禿了。本文提供斷點(diǎn)續(xù)傳下載工具方法,希望可以幫助到你
    2021-11-11
  • Python的列表常用方法詳解

    Python的列表常用方法詳解

    本文介紹了Python語(yǔ)言的列表及其操作方法,還介紹了列表的運(yùn)算,通過(guò)實(shí)例,展示了列表生成式和傳統(tǒng)方法的對(duì)比,以及如何使用生成式對(duì)列表進(jìn)行高效的生成和操作,感興趣的朋友一起看看吧
    2019-06-06
  • PyTorch變分自編碼器的構(gòu)建與應(yīng)用小結(jié)

    PyTorch變分自編碼器的構(gòu)建與應(yīng)用小結(jié)

    變分自編碼器是一種強(qiáng)大的深度學(xué)習(xí)模型,用于學(xué)習(xí)數(shù)據(jù)的潛在表示并能生成新的數(shù)據(jù)點(diǎn),使用PyTorch實(shí)現(xiàn)VAE不僅可以加深對(duì)生成模型的理解,還可以利用其靈活性進(jìn)行各種實(shí)驗(yàn),這篇文章主要介紹了PyTorch變分自編碼器的構(gòu)建與應(yīng)用,需要的朋友可以參考下
    2024-07-07
  • Python 爬蟲(chóng)爬取指定博客的所有文章

    Python 爬蟲(chóng)爬取指定博客的所有文章

    因?yàn)镚oogle App Engine 被墻,我無(wú)法繼續(xù)完善我的Moven project 還有20+天才回去,怕到時(shí)候會(huì)忘記project的進(jìn)度和細(xì)節(jié)就趁著個(gè)冷的什么都不想干的時(shí)候, 大概的總結(jié)一下
    2016-02-02
  • 用vue.js組件模擬v-model指令實(shí)例方法

    用vue.js組件模擬v-model指令實(shí)例方法

    在本篇文章里小編給各位整理的是關(guān)于用vue.js組件模擬v-model指令實(shí)例方法和相關(guān)代碼,需要的朋友們可以跟著學(xué)習(xí)下。
    2019-07-07
  • python io.BytesIO簡(jiǎn)介及示例代碼

    python io.BytesIO簡(jiǎn)介及示例代碼

    io.BytesIO 是 Python 內(nèi)置的一個(gè) I/O 類,用于在內(nèi)存中讀寫(xiě)二進(jìn)制數(shù)據(jù),這篇文章主要介紹了python io.BytesIO簡(jiǎn)要介紹及示例,需要的朋友可以參考下
    2023-05-05
  • Python實(shí)現(xiàn)AI自動(dòng)摳圖實(shí)例解析

    Python實(shí)現(xiàn)AI自動(dòng)摳圖實(shí)例解析

    這篇文章主要介紹了Python實(shí)現(xiàn)AI自動(dòng)摳圖實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03

最新評(píng)論

宜州市| 武陟县| 吉木乃县| 鹿邑县| 云龙县| 瑞金市| 彭山县| 湟中县| 栾城县| 阜平县| 睢宁县| 怀安县| 芮城县| 平远县| 渑池县| 玉田县| 乌苏市| 安图县| 收藏| 平凉市| 木兰县| 定陶县| 安国市| 宁远县| 噶尔县| 益阳市| 秦安县| 漾濞| 绥德县| 循化| 静宁县| 福贡县| 南宁市| 长乐市| 定远县| 明光市| 柘荣县| 余干县| 金溪县| 宜兴市| 靖远县|