首页 > 解决方案 > 当我 dockerize 我的 Go 应用程序时,它没有看到 HTML 模板

问题描述

我正在尝试使用 gomail 编写通知服务。当我在本地运行我的应用程序时,一切正常,但是当我 dockerize 我的应用程序时,我得到零指针错误,因为 gomail 找不到 html 模板。

错误:

app_1  | panic: runtime error: invalid memory address or nil pointer dereference
app_1  | [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x41dc7c]

这是我的码头文件:

FROM golang@.. as builder

RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates

RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/noneexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    "${USER}"

WORKDIR /app

COPY go.mod .
COPY go.sum .

RUN go mod download
RUN go mod verify

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

FROM scratch

COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
COPY --from=builder /app/main .


EXPOSE 8000

ENTRYPOINT ["./main"]

我的文件夹结构:

notification-service
├── Makefile
├── README.md
├── common
│   ├── constants.go
│   ├── env.go
│   └── logger.go
├── docker-compose.yml
├── dockerfile
├── entities
│   └── invite_struct.go
├── go.mod
├── go.sum
├── main.go
├── router.go
├── services
│   └── mail.go
├── templates
│   ├── info.html
│   └── invite.html
├── kafka
│   ├── consumer.go
│   └── producer.go

代码:

卡夫卡/consumer.go

if ok := mailService.Send("templates/info.html", inviteMessage.Email, "Account Created", content); !ok {
                continue
            }

服务/mail.go

type MailServiceInterface interface {
    Send(template string, to string, subject string, data interface{}) bool
}

type Mail struct {
}

func NewMailServiceClient() MailServiceInterface {
    return &Mail{}
}

func (m *Mail) Send(template string, to string, subject string, data interface{}) bool {
    t, _ := m.parseTemplate(template, data)
    if ok := m.sendMail(*t, to, subject); ok {
        log.Println(fmt.Sprintf("Email has been sent to %s", to))
        return true
    } else {
        log.Println(fmt.Sprintf("Failed to send the email to %s", to))
        return false
    }
}

func (m *Mail) parseTemplate(templateFileName string, data interface{}) (*string, error) {
    t, err := template.ParseFiles(templateFileName)
    if err != nil {
        return nil, err
    }
    buf := new(bytes.Buffer)
    if err = t.Execute(buf, data); err != nil {
        return nil, err
    }
    body := buf.String()
    return &body, nil
}

func (m *Mail) sendMail(template, to, subject string) bool {
    env := common.GetEnvironment()
    from := env.MailUsername
    password := env.MailPassword
    port, _ := strconv.Atoi(env.MailPort)
    mailServer := env.MailServer

    mail := gomail.NewMessage()
    mail.SetHeader("From", from)
    mail.SetHeader("To", to)
    mail.SetHeader("Subject", subject)
    mail.SetBody("text/html", template)

    d := gomail.NewDialer(mailServer, port, from, password)

    if err := d.DialAndSend(mail); err != nil {
        zap.S().Error(err)
        return false
    }
    return true
}

标签: dockergo

解决方案


在您运行的 dockerfile 结束时,您COPY --from=builder /app/main .只复制可执行文件。

但是template.ParseFiles(templateFileName)实现使用os.ReadFile(file),这意味着它在文件系统中查找文件。这些文件不存在,因为您只复制了可执行文件。

如果您不想更改构建过程并且使用 Go 1.16 或更高版本,embed请将 html 文件放入可执行文件中:

文件夹结构:

├── templates
│   ├── info.html
│   └── invite.html
│   └── templates.go <-- new file

模板.go

package templates

import _ "embed"

//go:embed info.html
var Info string

//go:embed invite.html
var Invite string

服务/mail.go

t, err := template.New("").Parse(templates.Info)

推荐阅读