我正在尝试通过商店功能上传和创建生成的 pdf 的下载提示,但我不断收到错误,就好像尚未生成 pdf,但我可以确认有一个 pdf 文件正在本地保存.
这是我的 pdf 处理程序:
defp handle_export_pdf(template_name, report_name, data) do
filename = "#{report_name}.pdf"
tmp_dir = "#{System.tmp_dir()}"
dir_path = "#{tmp_dir}/#{filename}"
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p!(tmp_dir)},
{:write_file, :ok} <-
{:write_file, generate_pdf(data, report_name, dir_path)},
{:store_on_cloud, {:ok, file}} <-
{:store_on_cloud, HandOffFileUploader.store({dir_path, filename})},
{:delete_file, :ok} <- {:delete_file, File.rm(dir_path)} do
{:ok, file}
else
{:create_dir, _error} ->
{:error, "Error creating directory"}
{:write_file, _error} ->
{:error, "Error writing data to file"}
{:store_on_cloud, _error} ->
{:error, "Error uploading file"}
{:delete_file, _error} ->
{:error, "Error deleting file"}
end
end
尝试存储时发生错误,因为我收到错误“上传文件时出错”,同时出现以下错误:
[error] GenServer #PID<0.143080.0> terminating ** (Plug.Conn.NotSentError) a response was neither set nor sent from the connection
这是我的generate_pdf函数,它处理pdf的生成:
def generate_pdf(data, title, path) do
html =
Sneeze.render([
:html,
[
:body,
%{
style:
style(%{
"font-family" => "Helvetica",
"font-size" => "20pt"
})
},
render_header(title),
render_table(data)
]
])
{:ok, filename} = PdfGenerator.generate(html, page_size: "A3", shell_params: ["--dpi", "300"])
File.rename(filename, path)
:ok
end
这是我的完整控制器代码。发生了很多事情,当我让它工作时我会重构它。但这还包括一个 xlsx 处理程序,可以很好地上传和提示下载。由于某种原因,该问题仅存在于 pdf 中。
defmodule EpmsWeb.ReportExportController do
use EpmsWeb, :controller
alias Epms.Delivery
alias Epms.Assets
alias Epms.Repo
alias Epms.HandOffFileUploader
alias Elixlsx.Workbook
alias Elixlsx.Sheet
@report_names %{
"a" => "Title A",
"b" => "Title B",
"c" => "Title C",
}
def export_report(conn, %{
"file_type" => file_type,
"frequency_type" => frequency_type,
"from_date" => from_date,
"to_date" => to_date,
"template_name" => template_name
}) do
report_name = @report_names[template_name]
assigns = fetch_report_data(conn, template_name, file_type)
filename_result =
case file_type do
"xlsx" ->
handle_export_xlsx(template_name, report_name, assigns)
"pdf" ->
handle_export_pdf(template_name, report_name, assigns)
_ ->
{:error, :unsupported_file_type}
end
case filename_result do
{:ok, filename} ->
conn
|> redirect(external: HandOffFileUploader.url(filename, signed: true))
|> halt()
_ ->
conn
|> put_flash(:error, "Unsupported file type or error in generating report.")
|> halt()
end
end
defp fetch_report_data(conn, template_name, file_type) do
case {template_name, file_type} do
{"a", "xlsx"} ->
Epms.ExportsHelper.a.get_rows(conn)
{"b", "xlsx"} ->
Epms.ExportsHelper.b.get_rows(conn)
{"c", "xlsx"} ->
Epms.ExportsHelper.c.get_rows(conn)
{"a", "pdf"} ->
Epms.ExportsHelper.a.get_rows(conn)
{"b", "pdf"} ->
Epms.ExportsHelper.b.get_rows(conn)
{"c", "pdf"} ->
Epms.ExportsHelper.c.get_rows(conn)
# Add more cases for different file types as needed
_ ->
%{}
end
end
defp handle_export_xlsx(template_name, report_name, data) do
# Use the filename_for function to generate a filename
filename = "#{report_name}.xlsx"
tmp_dir = "#{System.tmp_dir()}"
dir_path = "#{tmp_dir}/#{filename}"
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p!(tmp_dir)},
{:write_file, :ok} <-
{:write_file, write_data_to_file(template_name, dir_path, data)},
{:store_on_cloud, {:ok, file}} <-
{:store_on_cloud, HandOffFileUploader.store({dir_path, filename})},
{:delete_file, :ok} <- {:delete_file, File.rm(dir_path)} do
{:ok, file}
else
{:create_dir, _error} ->
{:error, "Error creating directory"}
{:write_file, _error} ->
{:error, "Error writing data to file"}
{:store_on_cloud, _error} ->
{:error, "Error uploading file"}
{:delete_file, _error} ->
{:error, "Error deleting file"}
end
end
defp write_data_to_file(report_template, path, data) do
report_codes = Enum.map(@report_names, fn {code, _name} -> code end)
if report_template in report_codes do
write_xlsx(path, data)
end
end
defp handle_export_pdf(template_name, report_name, data) do
filename = "#{report_name}.pdf"
tmp_dir = "#{System.tmp_dir()}"
dir_path = "#{tmp_dir}/#{filename}"
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p!(tmp_dir)},
{:write_file, :ok} <-
{:write_file, generate_pdf(data, report_name, dir_path)},
{:store_on_cloud, {:ok, file}} <-
{:store_on_cloud, HandOffFileUploader.store({dir_path, filename})},
{:delete_file, :ok} <- {:delete_file, File.rm(dir_path)} do
{:ok, file}
else
{:create_dir, _error} ->
{:error, "Error creating directory"}
{:write_file, _error} ->
{:error, "Error writing data to file"}
{:store_on_cloud, _error} ->
{:error, "Error uploading file"}
{:delete_file, _error} ->
{:error, "Error deleting file"}
end
end
defp write_xlsx(path, rows) do
# Assuming `rows` is a list of lists, where each inner list represents a row in the sheet
max_columns = Enum.max_by(rows, &length/1) |> length
# Generate a map with the same width for all columns
uniform_col_widths = 1..max_columns |> Enum.map(&{&1, 20}) |> Enum.into(%{})
# Write the xlsx file
sheet1 = %Sheet{name: "Export", rows: rows, col_widths: uniform_col_widths}
workbook = %Workbook{sheets: [sheet1]}
workbook
|> Elixlsx.write_to(path)
:ok
end
defp format_date_to_string(date) when is_nil(date), do: ""
defp format_date_to_string(%Date{} = date), do: Date.to_string(date)
# PDF GENERATION
def generate_pdf(data, title, path) do
html =
Sneeze.render([
:html,
[
:body,
%{
style:
style(%{
"font-family" => "Helvetica",
"font-size" => "20pt"
})
},
render_header(title),
render_table(data)
]
])
{:ok, filename} = PdfGenerator.generate(html, page_size: "A3", shell_params: ["--dpi", "300"])
File.rename(filename, path)
:ok
end
defp style(style_map) do
style_map
|> Enum.map(fn {key, value} ->
"#{key}: #{value}"
end)
|> Enum.join(";")
end
defp render_header(title) do
date = DateTime.utc_now()
date_string = "#{date.year}/#{date.month}/#{date.day}"
[
:div,
%{
style:
style(%{
"display" => "flex",
"flex-direction" => "column",
"align-items" => "flex-start",
})
},
[
:div,
%{
style:
style(%{
"display" => "inline-block",
"margin-top" => "10pt"
})
},
[
:h1,
%{
style:
style(%{
"font-size" => "16pt",
"margin-top" => "0pt",
"padding-top" => "0pt"
})
},
title
],
[
:h3,
%{
style:
style(%{
"font-size" => "14pt",
})
},
date_string
]
]
]
end
defp render_table(data) do
table = [
:table,
%{
style:
style(%{
"border" => "1px solid black",
"border-collapse" => "collapse",
"width" => "100%"
})
},
]
rows = Enum.map(data, &render_row/1)
table ++ rows
end
defp render_row(data) do
row = [
:tr,
%{
style:
style(%{
"border" => "1px solid black",
"border-collapse" => "collapse"
})
},
]
items = Enum.map(data, &render_items/1)
row ++ items
end
defp render_items(item) do
[
:td,
%{
style:
style(%{
"border" => "1px solid black",
"border-collapse" => "collapse",
"padding" => "5pt"
})
},
[
:span,
%{
style:
style(%{
"font-size" => "12pt",
"margin-top" => "0pt",
"padding-top" => "0pt"
})
},
item
],
]
end
end
修复了我的上传文件中实际存在的问题。我必须将 .pdf 添加到白名单文件扩展名列表中。
def validate({file, _}) do
file_extension = file.file_name |> Path.extname() |> String.downcase()
case Enum.member?(~w(.txt .csv .xlsx .xls .pdf), file_extension) do
true -> :ok
false -> {:error, "invalid file type"}
end
结束