2
0

routes_file_upload.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import { Hono } from "hono";
  2. import debug from "debug";
  3. import type {
  4. FileLibrary,
  5. } from "../client/share/types.ts";
  6. import {
  7. DeleteStatus,
  8. EnableStatus,
  9. } from "../client/share/types.ts";
  10. import type { Variables, WithAuth } from "./app.tsx";
  11. const log = {
  12. api: debug("api:sys"),
  13. };
  14. // 创建文件上传路由
  15. export function createFileUploadRoutes(withAuth: WithAuth) {
  16. const fileUploadRoutes = new Hono<{ Variables: Variables }>();
  17. // 获取 MinIO 上传策略
  18. fileUploadRoutes.get("/policy", withAuth, async (c) => {
  19. try {
  20. const prefix = c.req.query("prefix") || "uploads/";
  21. const filename = c.req.query("filename");
  22. const maxSize = Number(c.req.query("maxSize")) || 10 * 1024 * 1024; // 默认10MB
  23. if (!filename) {
  24. return c.json({ error: "文件名不能为空" }, 400);
  25. }
  26. const apiClient = c.get('apiClient');
  27. const policy = await apiClient.storage.getUploadPolicy(
  28. prefix,
  29. filename,
  30. maxSize
  31. );
  32. return c.json({
  33. message: "获取上传策略成功",
  34. data: policy,
  35. });
  36. } catch (error) {
  37. log.api("获取上传策略失败:", error);
  38. return c.json({ error: "获取上传策略失败" }, 500);
  39. }
  40. });
  41. // 保存文件信息到文件库
  42. fileUploadRoutes.post("/save", withAuth, async (c) => {
  43. try {
  44. const fileData = (await c.req.json()) as Partial<FileLibrary>;
  45. const user = c.get("user");
  46. // 验证必填字段
  47. if (!fileData.file_name || !fileData.file_path || !fileData.file_type) {
  48. return c.json({ error: "文件名、路径和类型不能为空" }, 400);
  49. }
  50. const apiClient = c.get('apiClient');
  51. // 设置上传者信息
  52. if (user) {
  53. fileData.uploader_id = user.id;
  54. fileData.uploader_name = user.nickname || user.username;
  55. }
  56. // 插入文件库记录
  57. const [id] = await apiClient.database.table("file_library").insert({
  58. ...fileData,
  59. download_count: 0,
  60. is_disabled: EnableStatus.ENABLED,
  61. is_deleted: DeleteStatus.NOT_DELETED,
  62. created_at: apiClient.database.fn.now(),
  63. updated_at: apiClient.database.fn.now(),
  64. });
  65. // 获取插入的数据
  66. const [insertedFile] = await apiClient.database
  67. .table("file_library")
  68. .where("id", id);
  69. return c.json({
  70. message: "文件信息保存成功",
  71. data: insertedFile,
  72. });
  73. } catch (error) {
  74. log.api("保存文件信息失败:", error);
  75. return c.json({ error: "保存文件信息失败" }, 500);
  76. }
  77. });
  78. // 获取文件列表
  79. fileUploadRoutes.get("/list", withAuth, async (c) => {
  80. try {
  81. const page = Number(c.req.query("page")) || 1;
  82. const pageSize = Number(c.req.query("pageSize")) || 10;
  83. const category_id = c.req.query("category_id");
  84. const fileType = c.req.query("fileType");
  85. const keyword = c.req.query("keyword");
  86. const apiClient = c.get('apiClient');
  87. let query = apiClient.database
  88. .table("file_library")
  89. .where("is_deleted", DeleteStatus.NOT_DELETED)
  90. .orderBy("created_at", "desc");
  91. // 应用过滤条件
  92. if (category_id) {
  93. query = query.where("category_id", category_id);
  94. }
  95. if (fileType) {
  96. query = query.where("file_type", fileType);
  97. }
  98. if (keyword) {
  99. query = query.where((builder) => {
  100. builder
  101. .where("file_name", "like", `%${keyword}%`)
  102. .orWhere("description", "like", `%${keyword}%`)
  103. .orWhere("tags", "like", `%${keyword}%`);
  104. });
  105. }
  106. // 获取总数
  107. const total = await query.clone().count();
  108. // 分页查询
  109. const files = await query.limit(pageSize).offset((page - 1) * pageSize);
  110. return c.json({
  111. message: "获取文件列表成功",
  112. data: {
  113. list: files,
  114. pagination: {
  115. current: page,
  116. pageSize,
  117. total: Number(total),
  118. },
  119. },
  120. });
  121. } catch (error) {
  122. log.api("获取文件列表失败:", error);
  123. return c.json({ error: "获取文件列表失败" }, 500);
  124. }
  125. });
  126. // 获取单个文件信息
  127. fileUploadRoutes.get("/:id", withAuth, async (c) => {
  128. try {
  129. const id = Number(c.req.param("id"));
  130. if (!id || isNaN(id)) {
  131. return c.json({ error: "无效的文件ID" }, 400);
  132. }
  133. const apiClient = c.get('apiClient');
  134. const file = await apiClient.database
  135. .table("file_library")
  136. .where("id", id)
  137. .where("is_deleted", DeleteStatus.NOT_DELETED)
  138. .first();
  139. if (!file) {
  140. return c.json({ error: "文件不存在" }, 404);
  141. }
  142. return c.json({
  143. message: "获取文件信息成功",
  144. data: file,
  145. });
  146. } catch (error) {
  147. log.api("获取文件信息失败:", error);
  148. return c.json({ error: "获取文件信息失败" }, 500);
  149. }
  150. });
  151. // 增加文件下载计数
  152. fileUploadRoutes.post("/:id/download", withAuth, async (c) => {
  153. try {
  154. const id = Number(c.req.param("id"));
  155. if (!id || isNaN(id)) {
  156. return c.json({ error: "无效的文件ID" }, 400);
  157. }
  158. const apiClient = c.get('apiClient');
  159. // 查询文件是否存在
  160. const file = await apiClient.database
  161. .table("file_library")
  162. .where("id", id)
  163. .where("is_deleted", DeleteStatus.NOT_DELETED)
  164. .first();
  165. if (!file) {
  166. return c.json({ error: "文件不存在" }, 404);
  167. }
  168. // 增加下载计数
  169. await apiClient.database
  170. .table("file_library")
  171. .where("id", id)
  172. .update({
  173. download_count: apiClient.database.raw("download_count + 1"),
  174. updated_at: apiClient.database.fn.now(),
  175. });
  176. return c.json({
  177. message: "更新下载计数成功",
  178. });
  179. } catch (error) {
  180. log.api("更新下载计数失败:", error);
  181. return c.json({ error: "更新下载计数失败" }, 500);
  182. }
  183. });
  184. // 删除文件
  185. fileUploadRoutes.delete("/:id", withAuth, async (c) => {
  186. try {
  187. const id = Number(c.req.param("id"));
  188. if (!id || isNaN(id)) {
  189. return c.json({ error: "无效的文件ID" }, 400);
  190. }
  191. const apiClient = c.get('apiClient');
  192. // 查询文件是否存在
  193. const file = await apiClient.database
  194. .table("file_library")
  195. .where("id", id)
  196. .where("is_deleted", DeleteStatus.NOT_DELETED)
  197. .first();
  198. if (!file) {
  199. return c.json({ error: "文件不存在" }, 404);
  200. }
  201. // 软删除文件
  202. await apiClient.database.table("file_library").where("id", id).update({
  203. is_deleted: DeleteStatus.DELETED,
  204. updated_at: apiClient.database.fn.now(),
  205. });
  206. return c.json({
  207. message: "文件删除成功",
  208. });
  209. } catch (error) {
  210. log.api("删除文件失败:", error);
  211. return c.json({ error: "删除文件失败" }, 500);
  212. }
  213. });
  214. return fileUploadRoutes;
  215. }