2
0

routes_sys.ts 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063
  1. import { Hono } from "hono";
  2. import debug from "debug";
  3. import type {
  4. FileLibrary,
  5. FileCategory,
  6. KnowInfo,
  7. ThemeSettings,
  8. SystemSetting,
  9. SystemSettingGroupData,
  10. } from "../client/share/types.ts";
  11. import {
  12. EnableStatus,
  13. DeleteStatus,
  14. ThemeMode,
  15. FontSize,
  16. CompactMode,
  17. } from "../client/share/types.ts";
  18. import type { Variables, WithAuth } from "./app.tsx";
  19. const log = {
  20. api: debug("api:sys"),
  21. };
  22. // 创建知识库管理路由
  23. export function createKnowInfoRoutes(withAuth: WithAuth) {
  24. const knowInfoRoutes = new Hono<{ Variables: Variables }>();
  25. // 获取知识库文章列表
  26. knowInfoRoutes.get("/", withAuth, async (c) => {
  27. try {
  28. const apiClient = c.get('apiClient');
  29. // 获取分页参数
  30. const page = Number(c.req.query("page")) || 1;
  31. const limit = Number(c.req.query("limit")) || 10;
  32. const offset = (page - 1) * limit;
  33. // 获取筛选参数
  34. const title = c.req.query("title");
  35. const category = c.req.query("category");
  36. const tags = c.req.query("tags");
  37. // 构建查询
  38. let query = apiClient.database
  39. .table("know_info")
  40. .where("is_deleted", 0)
  41. .orderBy("id", "desc");
  42. // 应用筛选条件
  43. if (title) {
  44. query = query.where("title", "like", `%${title}%`);
  45. }
  46. if (category) {
  47. query = query.where("category", category);
  48. }
  49. if (tags) {
  50. query = query.where("tags", "like", `%${tags}%`);
  51. }
  52. // 克隆查询以获取总数
  53. const countQuery = query.clone();
  54. // 执行分页查询
  55. const articles = await query.limit(limit).offset(offset);
  56. // 获取总数
  57. const count = await countQuery.count();
  58. return c.json({
  59. data: articles,
  60. pagination: {
  61. total: Number(count),
  62. current: page,
  63. pageSize: limit,
  64. totalPages: Math.ceil(Number(count) / limit),
  65. },
  66. });
  67. } catch (error) {
  68. log.api("获取知识库文章列表失败:", error);
  69. return c.json({ error: "获取知识库文章列表失败" }, 500);
  70. }
  71. });
  72. // 获取单个知识库文章
  73. knowInfoRoutes.get("/:id", withAuth, async (c) => {
  74. try {
  75. const id = Number(c.req.param("id"));
  76. if (!id || isNaN(id)) {
  77. return c.json({ error: "无效的文章ID" }, 400);
  78. }
  79. const apiClient = c.get('apiClient');
  80. const [article] = await apiClient.database
  81. .table("know_info")
  82. .where({ id, is_deleted: 0 });
  83. if (!article) {
  84. return c.json({ error: "文章不存在" }, 404);
  85. }
  86. return c.json({message: '获取知识库文章详情成功', data: article});
  87. } catch (error) {
  88. log.api("获取知识库文章详情失败:", error);
  89. return c.json({ error: "获取知识库文章详情失败" }, 500);
  90. }
  91. });
  92. // 创建知识库文章
  93. knowInfoRoutes.post("/", withAuth, async (c) => {
  94. try {
  95. const articleData = (await c.req.json()) as Partial<KnowInfo>;
  96. // 验证必填字段
  97. if (!articleData.title) {
  98. return c.json({ error: "文章标题不能为空" }, 400);
  99. }
  100. // 如果作者为空,则使用当前用户的用户名
  101. if (!articleData.author) {
  102. const user = c.get("user");
  103. articleData.author = user ? user.username : "unknown";
  104. }
  105. const apiClient = c.get('apiClient');
  106. const [id] = await apiClient.database
  107. .table("know_info")
  108. .insert(articleData);
  109. // 获取创建的文章
  110. const [createdArticle] = await apiClient.database
  111. .table("know_info")
  112. .where("id", id);
  113. return c.json({
  114. message: "知识库文章创建成功",
  115. data: createdArticle,
  116. });
  117. } catch (error) {
  118. log.api("创建知识库文章失败:", error);
  119. return c.json({ error: "创建知识库文章失败" }, 500);
  120. }
  121. });
  122. // 更新知识库文章
  123. knowInfoRoutes.put("/:id", withAuth, async (c) => {
  124. try {
  125. const id = Number(c.req.param("id"));
  126. if (!id || isNaN(id)) {
  127. return c.json({ error: "无效的文章ID" }, 400);
  128. }
  129. const articleData = (await c.req.json()) as Partial<KnowInfo>;
  130. // 验证必填字段
  131. if (!articleData.title) {
  132. return c.json({ error: "文章标题不能为空" }, 400);
  133. }
  134. const apiClient = c.get('apiClient');
  135. // 检查文章是否存在
  136. const [existingArticle] = await apiClient.database
  137. .table("know_info")
  138. .where({ id, is_deleted: 0 });
  139. if (!existingArticle) {
  140. return c.json({ error: "文章不存在" }, 404);
  141. }
  142. // 更新文章
  143. await apiClient.database
  144. .table("know_info")
  145. .where("id", id)
  146. .update({
  147. ...articleData,
  148. updated_at: apiClient.database.fn.now(),
  149. });
  150. // 获取更新后的文章
  151. const [updatedArticle] = await apiClient.database
  152. .table("know_info")
  153. .where("id", id);
  154. return c.json({
  155. message: "知识库文章更新成功",
  156. data: updatedArticle,
  157. });
  158. } catch (error) {
  159. log.api("更新知识库文章失败:", error);
  160. return c.json({ error: "更新知识库文章失败" }, 500);
  161. }
  162. });
  163. // 删除知识库文章(软删除)
  164. knowInfoRoutes.delete("/:id", withAuth, async (c) => {
  165. try {
  166. const id = Number(c.req.param("id"));
  167. if (!id || isNaN(id)) {
  168. return c.json({ error: "无效的文章ID" }, 400);
  169. }
  170. const apiClient = c.get('apiClient');
  171. // 检查文章是否存在
  172. const [existingArticle] = await apiClient.database
  173. .table("know_info")
  174. .where({ id, is_deleted: 0 });
  175. if (!existingArticle) {
  176. return c.json({ error: "文章不存在" }, 404);
  177. }
  178. // 软删除文章
  179. await apiClient.database.table("know_info").where("id", id).update({
  180. is_deleted: 1,
  181. updated_at: apiClient.database.fn.now(),
  182. });
  183. return c.json({
  184. message: "知识库文章删除成功",
  185. });
  186. } catch (error) {
  187. log.api("删除知识库文章失败:", error);
  188. return c.json({ error: "删除知识库文章失败" }, 500);
  189. }
  190. });
  191. return knowInfoRoutes;
  192. }
  193. // 创建文件分类路由
  194. export function createFileCategoryRoutes(withAuth: WithAuth) {
  195. const fileCategoryRoutes = new Hono<{ Variables: Variables }>();
  196. // 获取文件分类列表
  197. fileCategoryRoutes.get("/", withAuth, async (c) => {
  198. try {
  199. const apiClient = c.get('apiClient');
  200. const page = Number(c.req.query("page")) || 1;
  201. const pageSize = Number(c.req.query("pageSize")) || 10;
  202. const offset = (page - 1) * pageSize;
  203. const search = c.req.query("search") || "";
  204. let query = apiClient.database.table("file_category").orderBy("id", "desc");
  205. if (search) {
  206. query = query.where("name", "like", `%${search}%`);
  207. }
  208. const total = await query.clone().count();
  209. const categories = await query
  210. .select("id", "name", "code", "description")
  211. .limit(pageSize)
  212. .offset(offset);
  213. return c.json({
  214. data: categories,
  215. total: Number(total),
  216. page,
  217. pageSize,
  218. });
  219. } catch (error) {
  220. log.api("获取文件分类列表失败:", error);
  221. return c.json({ error: "获取文件分类列表失败" }, 500);
  222. }
  223. });
  224. // 创建文件分类
  225. fileCategoryRoutes.post("/", withAuth, async (c) => {
  226. try {
  227. const apiClient = c.get('apiClient');
  228. const data = (await c.req.json()) as Partial<FileCategory>;
  229. // 验证必填字段
  230. if (!data.name) {
  231. return c.json({ error: "分类名称不能为空" }, 400);
  232. }
  233. // 插入文件分类
  234. const [id] = await apiClient.database.table("file_category").insert({
  235. ...data,
  236. created_at: apiClient.database.fn.now(),
  237. updated_at: apiClient.database.fn.now(),
  238. });
  239. return c.json({
  240. message: "文件分类创建成功",
  241. data: {
  242. id,
  243. ...data,
  244. },
  245. });
  246. } catch (error) {
  247. log.api("创建文件分类失败:", error);
  248. return c.json({ error: "创建文件分类失败" }, 500);
  249. }
  250. });
  251. // 更新文件分类
  252. fileCategoryRoutes.put("/:id", withAuth, async (c) => {
  253. try {
  254. const apiClient = c.get('apiClient');
  255. const id = Number(c.req.param("id"));
  256. if (!id || isNaN(id)) {
  257. return c.json({ error: "无效的分类ID" }, 400);
  258. }
  259. const data = (await c.req.json()) as Partial<FileCategory>;
  260. // 更新文件分类
  261. await apiClient.database
  262. .table("file_category")
  263. .where("id", id)
  264. .update({
  265. ...data,
  266. updated_at: apiClient.database.fn.now(),
  267. });
  268. return c.json({
  269. message: "文件分类更新成功",
  270. data: {
  271. id,
  272. ...data,
  273. },
  274. });
  275. } catch (error) {
  276. log.api("更新文件分类失败:", error);
  277. return c.json({ error: "更新文件分类失败" }, 500);
  278. }
  279. });
  280. // 删除文件分类
  281. fileCategoryRoutes.delete("/:id", withAuth, async (c) => {
  282. try {
  283. const apiClient = c.get('apiClient');
  284. const id = Number(c.req.param("id"));
  285. if (!id || isNaN(id)) {
  286. return c.json({ error: "无效的分类ID" }, 400);
  287. }
  288. await apiClient.database.table("file_category").where("id", id).update({
  289. is_deleted: DeleteStatus.DELETED,
  290. updated_at: apiClient.database.fn.now(),
  291. });
  292. return c.json({
  293. message: "文件分类删除成功",
  294. });
  295. } catch (error) {
  296. log.api("删除文件分类失败:", error);
  297. return c.json({ error: "删除文件分类失败" }, 500);
  298. }
  299. });
  300. return fileCategoryRoutes;
  301. }
  302. // 创建文件上传路由
  303. export function createFileUploadRoutes(withAuth: WithAuth) {
  304. const fileUploadRoutes = new Hono<{ Variables: Variables }>();
  305. // 获取 MinIO 上传策略
  306. fileUploadRoutes.get("/policy", withAuth, async (c) => {
  307. try {
  308. const prefix = c.req.query("prefix") || "uploads/";
  309. const filename = c.req.query("filename");
  310. const maxSize = Number(c.req.query("maxSize")) || 10 * 1024 * 1024; // 默认10MB
  311. if (!filename) {
  312. return c.json({ error: "文件名不能为空" }, 400);
  313. }
  314. const apiClient = c.get('apiClient');
  315. const policy = await apiClient.storage.getUploadPolicy(
  316. prefix,
  317. filename,
  318. maxSize
  319. );
  320. return c.json({
  321. message: "获取上传策略成功",
  322. data: policy,
  323. });
  324. } catch (error) {
  325. log.api("获取上传策略失败:", error);
  326. return c.json({ error: "获取上传策略失败" }, 500);
  327. }
  328. });
  329. // 保存文件信息到文件库
  330. fileUploadRoutes.post("/save", withAuth, async (c) => {
  331. try {
  332. const fileData = (await c.req.json()) as Partial<FileLibrary>;
  333. const user = c.get("user");
  334. // 验证必填字段
  335. if (!fileData.file_name || !fileData.file_path || !fileData.file_type) {
  336. return c.json({ error: "文件名、路径和类型不能为空" }, 400);
  337. }
  338. const apiClient = c.get('apiClient');
  339. // 设置上传者信息
  340. if (user) {
  341. fileData.uploader_id = user.id;
  342. fileData.uploader_name = user.nickname || user.username;
  343. }
  344. // 插入文件库记录
  345. const [id] = await apiClient.database.table("file_library").insert({
  346. ...fileData,
  347. download_count: 0,
  348. is_disabled: EnableStatus.ENABLED,
  349. is_deleted: DeleteStatus.NOT_DELETED,
  350. created_at: apiClient.database.fn.now(),
  351. updated_at: apiClient.database.fn.now(),
  352. });
  353. // 获取插入的数据
  354. const [insertedFile] = await apiClient.database
  355. .table("file_library")
  356. .where("id", id);
  357. return c.json({
  358. message: "文件信息保存成功",
  359. data: insertedFile,
  360. });
  361. } catch (error) {
  362. log.api("保存文件信息失败:", error);
  363. return c.json({ error: "保存文件信息失败" }, 500);
  364. }
  365. });
  366. // 获取文件列表
  367. fileUploadRoutes.get("/list", withAuth, async (c) => {
  368. try {
  369. const page = Number(c.req.query("page")) || 1;
  370. const pageSize = Number(c.req.query("pageSize")) || 10;
  371. const category_id = c.req.query("category_id");
  372. const fileType = c.req.query("fileType");
  373. const keyword = c.req.query("keyword");
  374. const apiClient = c.get('apiClient');
  375. let query = apiClient.database
  376. .table("file_library")
  377. .where("is_deleted", DeleteStatus.NOT_DELETED)
  378. .orderBy("created_at", "desc");
  379. // 应用过滤条件
  380. if (category_id) {
  381. query = query.where("category_id", category_id);
  382. }
  383. if (fileType) {
  384. query = query.where("file_type", fileType);
  385. }
  386. if (keyword) {
  387. query = query.where((builder) => {
  388. builder
  389. .where("file_name", "like", `%${keyword}%`)
  390. .orWhere("description", "like", `%${keyword}%`)
  391. .orWhere("tags", "like", `%${keyword}%`);
  392. });
  393. }
  394. // 获取总数
  395. const total = await query.clone().count();
  396. // 分页查询
  397. const files = await query.limit(pageSize).offset((page - 1) * pageSize);
  398. return c.json({
  399. message: "获取文件列表成功",
  400. data: {
  401. list: files,
  402. pagination: {
  403. current: page,
  404. pageSize,
  405. total: Number(total),
  406. },
  407. },
  408. });
  409. } catch (error) {
  410. log.api("获取文件列表失败:", error);
  411. return c.json({ error: "获取文件列表失败" }, 500);
  412. }
  413. });
  414. // 获取单个文件信息
  415. fileUploadRoutes.get("/:id", withAuth, async (c) => {
  416. try {
  417. const id = Number(c.req.param("id"));
  418. if (!id || isNaN(id)) {
  419. return c.json({ error: "无效的文件ID" }, 400);
  420. }
  421. const apiClient = c.get('apiClient');
  422. const file = await apiClient.database
  423. .table("file_library")
  424. .where("id", id)
  425. .where("is_deleted", DeleteStatus.NOT_DELETED)
  426. .first();
  427. if (!file) {
  428. return c.json({ error: "文件不存在" }, 404);
  429. }
  430. return c.json({
  431. message: "获取文件信息成功",
  432. data: file,
  433. });
  434. } catch (error) {
  435. log.api("获取文件信息失败:", error);
  436. return c.json({ error: "获取文件信息失败" }, 500);
  437. }
  438. });
  439. // 增加文件下载计数
  440. fileUploadRoutes.post("/:id/download", withAuth, async (c) => {
  441. try {
  442. const id = Number(c.req.param("id"));
  443. if (!id || isNaN(id)) {
  444. return c.json({ error: "无效的文件ID" }, 400);
  445. }
  446. const apiClient = c.get('apiClient');
  447. // 查询文件是否存在
  448. const file = await apiClient.database
  449. .table("file_library")
  450. .where("id", id)
  451. .where("is_deleted", DeleteStatus.NOT_DELETED)
  452. .first();
  453. if (!file) {
  454. return c.json({ error: "文件不存在" }, 404);
  455. }
  456. // 增加下载计数
  457. await apiClient.database
  458. .table("file_library")
  459. .where("id", id)
  460. .update({
  461. download_count: apiClient.database.raw("download_count + 1"),
  462. updated_at: apiClient.database.fn.now(),
  463. });
  464. return c.json({
  465. message: "更新下载计数成功",
  466. });
  467. } catch (error) {
  468. log.api("更新下载计数失败:", error);
  469. return c.json({ error: "更新下载计数失败" }, 500);
  470. }
  471. });
  472. // 删除文件
  473. fileUploadRoutes.delete("/:id", withAuth, async (c) => {
  474. try {
  475. const id = Number(c.req.param("id"));
  476. if (!id || isNaN(id)) {
  477. return c.json({ error: "无效的文件ID" }, 400);
  478. }
  479. const apiClient = c.get('apiClient');
  480. // 查询文件是否存在
  481. const file = await apiClient.database
  482. .table("file_library")
  483. .where("id", id)
  484. .where("is_deleted", DeleteStatus.NOT_DELETED)
  485. .first();
  486. if (!file) {
  487. return c.json({ error: "文件不存在" }, 404);
  488. }
  489. // 软删除文件
  490. await apiClient.database.table("file_library").where("id", id).update({
  491. is_deleted: DeleteStatus.DELETED,
  492. updated_at: apiClient.database.fn.now(),
  493. });
  494. return c.json({
  495. message: "文件删除成功",
  496. });
  497. } catch (error) {
  498. log.api("删除文件失败:", error);
  499. return c.json({ error: "删除文件失败" }, 500);
  500. }
  501. });
  502. return fileUploadRoutes;
  503. }
  504. // 创建主题设置路由
  505. export function createThemeRoutes(withAuth: WithAuth) {
  506. const themeRoutes = new Hono<{ Variables: Variables }>();
  507. // 获取当前主题设置
  508. themeRoutes.get("/", withAuth, async (c) => {
  509. try {
  510. const apiClient = c.get('apiClient');
  511. const user = c.get('user');
  512. if (!user) {
  513. return c.json({ error: "未授权访问" }, 401);
  514. }
  515. // 获取用户的主题设置
  516. let themeSettings = await apiClient.database
  517. .table("theme_settings")
  518. .where("user_id", user.id)
  519. .first();
  520. // 如果用户没有主题设置,则创建默认设置
  521. if (!themeSettings) {
  522. const defaultSettings = {
  523. theme_mode: ThemeMode.LIGHT,
  524. primary_color: '#1890ff',
  525. font_size: FontSize.MEDIUM,
  526. is_compact: CompactMode.NORMAL
  527. };
  528. const [id] = await apiClient.database.table("theme_settings").insert({
  529. user_id: user.id,
  530. settings: defaultSettings,
  531. created_at: apiClient.database.fn.now(),
  532. updated_at: apiClient.database.fn.now(),
  533. });
  534. themeSettings = await apiClient.database
  535. .table("theme_settings")
  536. .where("id", id)
  537. .first();
  538. }
  539. return c.json({
  540. message: "获取主题设置成功",
  541. data: themeSettings?.settings,
  542. });
  543. } catch (error) {
  544. log.api("获取主题设置失败:", error);
  545. return c.json({ error: "获取主题设置失败" }, 500);
  546. }
  547. });
  548. // 更新主题设置
  549. themeRoutes.put("/", withAuth, async (c) => {
  550. try {
  551. const user = c.get('user');
  552. const apiClient = c.get('apiClient');
  553. if (!user) {
  554. return c.json({ error: "未授权访问" }, 401);
  555. }
  556. const themeData = (await c.req.json()) as Partial<ThemeSettings>;
  557. // 检查用户是否已有主题设置
  558. const existingTheme = await apiClient.database
  559. .table("theme_settings")
  560. .where("user_id", user.id)
  561. .first();
  562. if (existingTheme) {
  563. // 更新现有设置
  564. const currentSettings = existingTheme.settings || {};
  565. const updatedSettings = {
  566. ...currentSettings,
  567. ...themeData
  568. };
  569. await apiClient.database
  570. .table("theme_settings")
  571. .where("user_id", user.id)
  572. .update({
  573. settings: JSON.stringify(updatedSettings),
  574. updated_at: apiClient.database.fn.now(),
  575. });
  576. } else {
  577. // 创建新设置
  578. const defaultSettings = {
  579. theme_mode: ThemeMode.LIGHT,
  580. primary_color: '#1890ff',
  581. font_size: FontSize.MEDIUM,
  582. is_compact: CompactMode.NORMAL
  583. };
  584. const updatedSettings = {
  585. ...defaultSettings,
  586. ...themeData
  587. };
  588. await apiClient.database.table("theme_settings").insert({
  589. user_id: user.id,
  590. settings: updatedSettings,
  591. created_at: apiClient.database.fn.now(),
  592. updated_at: apiClient.database.fn.now(),
  593. });
  594. }
  595. // 获取更新后的主题设置
  596. const updatedTheme = await apiClient.database
  597. .table("theme_settings")
  598. .where("user_id", user.id)
  599. .first();
  600. return c.json({
  601. message: "主题设置更新成功",
  602. data: updatedTheme,
  603. });
  604. } catch (error) {
  605. log.api("更新主题设置失败:", error);
  606. return c.json({ error: "更新主题设置失败" }, 500);
  607. }
  608. });
  609. // 重置主题设置为默认值
  610. themeRoutes.post("/reset", withAuth, async (c) => {
  611. try {
  612. const user = c.get('user');
  613. const apiClient = c.get('apiClient');
  614. if (!user) {
  615. return c.json({ error: "未授权访问" }, 401);
  616. }
  617. // 默认主题设置
  618. const defaultSettings = {
  619. theme_mode: ThemeMode.LIGHT,
  620. primary_color: '#1890ff',
  621. font_size: FontSize.MEDIUM,
  622. is_compact: CompactMode.NORMAL
  623. };
  624. // 更新用户的主题设置
  625. await apiClient.database
  626. .table("theme_settings")
  627. .where("user_id", user.id)
  628. .update({
  629. settings: JSON.stringify(defaultSettings),
  630. updated_at: apiClient.database.fn.now(),
  631. });
  632. // 获取更新后的主题设置
  633. const updatedTheme = await apiClient.database
  634. .table("theme_settings")
  635. .where("user_id", user.id)
  636. .first();
  637. return c.json({
  638. message: "主题设置已重置为默认值",
  639. data: updatedTheme,
  640. });
  641. } catch (error) {
  642. log.api("重置主题设置失败:", error);
  643. return c.json({ error: "重置主题设置失败" }, 500);
  644. }
  645. });
  646. return themeRoutes;
  647. }
  648. // 创建系统设置路由
  649. export function createSystemSettingsRoutes(withAuth: WithAuth) {
  650. const settingsRoutes = new Hono<{ Variables: Variables }>();
  651. // 获取所有系统设置(按分组)
  652. settingsRoutes.get('/', withAuth, async (c) => {
  653. try {
  654. const apiClient = c.get('apiClient');
  655. const settings = await apiClient.database
  656. .table('system_settings')
  657. .select('*');
  658. // 按分组整理数据
  659. const groupedSettings = settings.reduce((acc: SystemSettingGroupData[], setting) => {
  660. const groupIndex = acc.findIndex((g: SystemSettingGroupData) => g.name === setting.group);
  661. if (groupIndex === -1) {
  662. acc.push({
  663. name: setting.group,
  664. description: `${setting.group}组设置`,
  665. settings: [{
  666. id: setting.id,
  667. key: setting.key,
  668. value: setting.value,
  669. description: setting.description,
  670. group: setting.group
  671. }]
  672. });
  673. } else {
  674. acc[groupIndex].settings.push({
  675. id: setting.id,
  676. key: setting.key,
  677. value: setting.value,
  678. description: setting.description,
  679. group: setting.group
  680. });
  681. }
  682. return acc;
  683. }, []);
  684. return c.json({
  685. message: '获取系统设置成功',
  686. data: groupedSettings
  687. });
  688. } catch (error) {
  689. log.api('获取系统设置失败:', error);
  690. return c.json({ error: '获取系统设置失败' }, 500);
  691. }
  692. });
  693. // 获取指定分组的系统设置
  694. settingsRoutes.get('/group/:group', withAuth, async (c) => {
  695. try {
  696. const apiClient = c.get('apiClient');
  697. const group = c.req.param('group');
  698. const settings = await apiClient.database
  699. .table('system_settings')
  700. .where('group', group)
  701. .select('*');
  702. return c.json({
  703. message: '获取分组设置成功',
  704. data: settings
  705. });
  706. } catch (error) {
  707. log.api('获取分组设置失败:', error);
  708. return c.json({ error: '获取分组设置失败' }, 500);
  709. }
  710. });
  711. // 更新系统设置
  712. settingsRoutes.put('/:key', withAuth, async (c) => {
  713. try {
  714. const apiClient = c.get('apiClient');
  715. const key = c.req.param('key');
  716. const settingData = await c.req.json();
  717. // 验证设置是否存在
  718. const existingSetting = await apiClient.database
  719. .table('system_settings')
  720. .where('key', key)
  721. .first();
  722. if (!existingSetting) {
  723. return c.json({ error: '设置项不存在' }, 404);
  724. }
  725. // 更新设置
  726. await apiClient.database
  727. .table('system_settings')
  728. .where('key', key)
  729. .update({
  730. value: settingData.value,
  731. updated_at: apiClient.database.fn.now()
  732. });
  733. // 获取更新后的设置
  734. const updatedSetting = await apiClient.database
  735. .table('system_settings')
  736. .where('key', key)
  737. .first();
  738. return c.json({
  739. message: '系统设置已更新',
  740. data: updatedSetting
  741. });
  742. } catch (error) {
  743. log.api('更新系统设置失败:', error);
  744. return c.json({ error: '更新系统设置失败' }, 500);
  745. }
  746. });
  747. // 批量更新系统设置
  748. settingsRoutes.put('/', withAuth, async (c) => {
  749. try {
  750. const apiClient = c.get('apiClient');
  751. const settingsData = await c.req.json();
  752. // 验证数据格式
  753. if (!Array.isArray(settingsData)) {
  754. return c.json({ error: '无效的请求数据格式,应为数组' }, 400);
  755. }
  756. const trxProvider = apiClient.database.transactionProvider();
  757. const trx = await trxProvider();
  758. for (const setting of settingsData) {
  759. if (!setting.key) continue;
  760. // 验证设置是否存在
  761. const existingSetting = await trx.table('system_settings')
  762. .where('key', setting.key)
  763. .first();
  764. if (!existingSetting) {
  765. throw new Error(`设置项 ${setting.key} 不存在`);
  766. }
  767. // 更新设置
  768. await trx.table('system_settings')
  769. .where('key', setting.key)
  770. .update({
  771. value: setting.value,
  772. updated_at: trx.fn.now()
  773. });
  774. }
  775. await trx.commit();
  776. // 获取所有更新后的设置
  777. const updatedSettings = await apiClient.database
  778. .table('system_settings')
  779. .whereIn('key', settingsData.map(s => s.key))
  780. .select('*');
  781. return c.json({
  782. message: '系统设置已批量更新',
  783. data: updatedSettings
  784. });
  785. } catch (error) {
  786. log.api('批量更新系统设置失败:', error);
  787. return c.json({ error: '批量更新系统设置失败' }, 500);
  788. }
  789. });
  790. // 重置系统设置
  791. settingsRoutes.post('/reset', withAuth, async (c) => {
  792. try {
  793. const apiClient = c.get('apiClient');
  794. // 重置为迁移文件中定义的初始值
  795. const trxProvider = apiClient.database.transactionProvider();
  796. const trx = await trxProvider();
  797. // 清空现有设置
  798. await trx.table('system_settings').delete();
  799. // 插入默认设置
  800. await trx.table('system_settings').insert([
  801. // 基础设置组
  802. {
  803. key: 'SITE_NAME',
  804. value: '应用管理系统',
  805. description: '站点名称',
  806. group: 'basic',
  807. created_at: apiClient.database.fn.now(),
  808. updated_at: apiClient.database.fn.now()
  809. },
  810. {
  811. key: 'SITE_DESCRIPTION',
  812. value: '一个强大的应用管理系统',
  813. description: '站点描述',
  814. group: 'basic',
  815. created_at: apiClient.database.fn.now(),
  816. updated_at: apiClient.database.fn.now()
  817. },
  818. {
  819. key: 'SITE_KEYWORDS',
  820. value: '应用管理,系统管理,后台管理',
  821. description: '站点关键词',
  822. group: 'basic',
  823. created_at: apiClient.database.fn.now(),
  824. updated_at: apiClient.database.fn.now()
  825. },
  826. // 功能设置组
  827. {
  828. key: 'ENABLE_REGISTER',
  829. value: 'true',
  830. description: '是否开启注册',
  831. group: 'feature',
  832. created_at: apiClient.database.fn.now(),
  833. updated_at: apiClient.database.fn.now()
  834. },
  835. {
  836. key: 'ENABLE_CAPTCHA',
  837. value: 'true',
  838. description: '是否开启验证码',
  839. group: 'feature',
  840. created_at: apiClient.database.fn.now(),
  841. updated_at: apiClient.database.fn.now()
  842. },
  843. {
  844. key: 'LOGIN_ATTEMPTS',
  845. value: '5',
  846. description: '登录尝试次数',
  847. group: 'feature',
  848. created_at: apiClient.database.fn.now(),
  849. updated_at: apiClient.database.fn.now()
  850. },
  851. // 上传设置组
  852. {
  853. key: 'UPLOAD_MAX_SIZE',
  854. value: '10',
  855. description: '最大上传大小(MB)',
  856. group: 'upload',
  857. created_at: apiClient.database.fn.now(),
  858. updated_at: apiClient.database.fn.now()
  859. },
  860. {
  861. key: 'ALLOWED_FILE_TYPES',
  862. value: 'jpg,jpeg,png,gif,doc,docx,xls,xlsx,pdf',
  863. description: '允许的文件类型',
  864. group: 'upload',
  865. created_at: apiClient.database.fn.now(),
  866. updated_at: apiClient.database.fn.now()
  867. },
  868. // 通知设置组
  869. {
  870. key: 'NOTIFY_ON_LOGIN',
  871. value: 'true',
  872. description: '登录通知',
  873. group: 'notify',
  874. created_at: apiClient.database.fn.now(),
  875. updated_at: apiClient.database.fn.now()
  876. },
  877. {
  878. key: 'NOTIFY_ON_ERROR',
  879. value: 'true',
  880. description: '错误通知',
  881. group: 'notify',
  882. created_at: apiClient.database.fn.now(),
  883. updated_at: apiClient.database.fn.now()
  884. }
  885. ]);
  886. await trx.commit();
  887. const resetSettings = await apiClient.database
  888. .table('system_settings')
  889. .select('*');
  890. return c.json({
  891. message: '系统设置已重置',
  892. data: resetSettings
  893. });
  894. } catch (error) {
  895. log.api('重置系统设置失败:', error);
  896. return c.json({ error: '重置系统设置失败' }, 500);
  897. }
  898. });
  899. return settingsRoutes;
  900. }