node.js - Node Azure 功能在本地运行,但在 Azure 中不起作用

标签 node.js typescript azure azure-functions

我正在开发 Node Azure Function(在 Linux 上)来动态创建 GIF 图像。它在我的本地 MacBook 上运行得非常好,但当我部署到 Azure 时却失败了。

当查看日志时,我只是看到它在部署到 Azure 时在日志中抛出 Microsoft.Azure.WebJobs.Script.Workers.Rpc.RpcException

下面是我正在进行的代码。我已经包含了完整的功能代码,因为(对我来说)没有明显的错误源指示。有谁知道可能导致此问题的原因吗?

import { AzureFunction, Context, HttpRequest } from "@azure/functions"
import { DateTime, Duration } from "luxon";
import { Canvas } from "canvas";
import * as fs from 'fs'; 

const os = require('os');
const { GifEncoder } = require("@skyra/gifenc")
const { buffer } = require('node:stream/consumers');

const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
    
    try {
        let countdownTo: string = req.query.countdownTo || DateTime.now().plus({days: 7}).toFormat("yyyy-LL-dd'T'T");
        let width: number = parseInt(req.query.width) || 300;
        let height: number = parseInt(req.query.height);
        let frames: number = parseInt(req.query.frames) || 180;
        let background: string = '#' + (req.query.background || '000e4e');
        let foreground: string = '#' + (req.query.foreground || '00b7f1');

        // Set height to 1/3 width if not specified
        if (!height) {
            height = width/3;
        }
        
        // ========================================================================

        // Set upper and lower limits 
        width = Math.max(width, Math.min(120, 640));
        height = Math.max(height, Math.min(120, 640));
        frames = Math.max(frames, Math.min(1, 600));
        
        // Time calculations
        const nowDate: DateTime = DateTime.now()
        const targetDate: DateTime = DateTime.fromISO(countdownTo); // "2022-11-01T12:00"
        let timeDifference: Duration = targetDate.diff(nowDate);

        // ========================================================================

        const encoder: any = new GifEncoder(width, height);
        const canvas: Canvas = new Canvas(width, height);
        const ctx: any = canvas.getContext('2d');

        let fileName = targetDate.toFormat('yyyyLLddHHmmss') + '-' + nowDate.toFormat('yyyyLLddHHmmss') + '.gif';
        // Specify temporary directory and create if it doesn't exist
        const temporaryFileDirectory: string = os.tmpdir() + '/tmp/';
        if (!fs.existsSync(temporaryFileDirectory)) {
            fs.mkdirSync(temporaryFileDirectory);
        }

        // Stream the GIF data into a file
        let filePath: string = temporaryFileDirectory + fileName + '.gif';
        const readStream = encoder.createReadStream()
        readStream.pipe(fs.createWriteStream(filePath));
        
        // Set the font size, style, and alignment
        let fontSize: string = Math.floor(width/12) + 'px';
        let fontFamily: string = 'Arial';
        ctx.font = [fontSize, fontFamily].join(' ');
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';

        // Start Encoding GIF
        encoder.setRepeat(0).setDelay(1000).setQuality(10).start();
        
        if (timeDifference.milliseconds <= 0) { // Date has passed
            context.log('EXPIRED Date:', countdownTo);
            
            // Set Canvas background color
            ctx.fillStyle = background;
            ctx.fillRect(0, 0, width, height);

            // Print Text
            ctx.fillStyle = foreground;
            ctx.fillText('Date has passed!', (width/2), (height/2));

            // Add frame to the GIF
            encoder.addFrame(ctx);
        }
        else {  // Date is in the future
            
            timeDifference = targetDate.diff(DateTime.now(), ['days', 'hours', 'minutes', 'seconds']);
            context.log('Date:', countdownTo);

            for (let i: number = 0; i < frames; i++) {
                
                // Extract the duration from the days
                let days: number = Math.floor(timeDifference.days);
                let hours: number = Math.floor(timeDifference.hours);
                let minutes: number = Math.floor(timeDifference.minutes);
                let seconds: number = Math.floor(timeDifference.seconds);

                let daysDisplay: any = (days.toString().length == 1) ? '0' + days : days;
                let hoursDisplay: any = (hours.toString().length == 1) ? '0' + hours : hours;
                let minutesDisplay: any = (minutes.toString().length == 1) ? '0' + minutes : minutes;
                let secondsDisplay: any = (seconds.toString().length == 1) ? '0' + seconds : seconds;

                // Create the text to be displayed
                let displayText: string = [daysDisplay, 'd ', hoursDisplay, 'h ', minutesDisplay, 'm ', secondsDisplay, 's'].join('');
                //context.log(displayText);

                // Set Canvas background color
                ctx.fillStyle = background;
                ctx.fillRect(0, 0, width, height);

                // Print Text
                ctx.fillStyle = foreground;
                ctx.fillText(displayText, (width/2), (height/2));

                // Add frame to the GIF
                encoder.addFrame(ctx);

                // Remove a second in preparation for the next frame
                if (seconds < 1) {
                    if (minutes < 1) {
                        if (hours < 1) {
                            timeDifference = timeDifference.minus({days: 1});
                        }
                        timeDifference = timeDifference.minus({hours: 1});
                    }
                    timeDifference = timeDifference.minus({minutes: 1});
                    timeDifference = timeDifference.plus({seconds: 59});
                }
                else {
                    timeDifference = timeDifference.minus({seconds: 1});
                }
                
            }
        }

        
        // Encoding complete...
        encoder.finish();

        const fileData = await buffer(readStream);
        //context.log(fileData);
        context.res = {
            headers: {
                "Content-Type": "image/gif"
            },
            isRaw: true,
            status: 200,
            body: new Uint8Array(fileData)
        };
        fs.unlinkSync(filePath)
    }
    catch(ex: any) {
        context.res = {
            status: 200,
            body: ex.message
        };
    }
};

export default httpTrigger;

最佳答案

我发现此问题与 Azure DevOps 管道的问题有关。管道表明它已成功运行,但它从未安装任何node_modules。

部署后,Function App 上的 node_modules 文件夹为空,导致对 Luxon 等内容的引用完全失败。

我切换到了 Github Actions,它运行良好,所以显然管道中的某些步骤不正确。

如果您遇到某个特定功能在本地完美运行但在生产中无法正常运行的问题,请仔细检查以确保您的 CI/CD 管道正常运行。

关于node.js - Node Azure 功能在本地运行,但在 Azure 中不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74318614/

相关文章:

typescript - 选取除指定属性之外的所有属性

node.js - 1>MDAVSCLI : error : EPERM, 不允许操作 'C:\Windows\CSC\v2.0.6'

node.js - 使用 Redis 存储来自 neo4j 的查询结果

ajax - NodeJS Web 应用程序文件上传截断文件开头

Angular 5 Material Table 用具有不同参数类型的函数替换 filterPredicate

reactjs - 为什么 TypeScript 看不到嵌套值不能未定义?

.net - 将 ASP.NET MVC 3 RC 发布到 Windows Azure

c# - 访问azure应用程序设置中的嵌套json应用程序设置返回null

azure - 从 Azure 应用服务轻松身份验证获取 Microsoft Graph 的 AccessToken

node.js - mongoose 中的 populate 函数是做什么的?