commit 6c6bcb985743d1e43f54de68697c24c6acac06f9 Author: zyh Date: Wed Apr 9 13:42:16 2025 +0000 init diff --git a/app.tsx b/app.tsx new file mode 100644 index 0000000..867c94b --- /dev/null +++ b/app.tsx @@ -0,0 +1,471 @@ +/** @jsxImportSource https://esm.d8d.fun/hono@4.7.4/jsx */ +import { Hono } from 'hono' +import { Auth } from '@d8d-appcontainer/auth' +import type { User as AuthUser } from '@d8d-appcontainer/auth' +import React from 'hono/jsx' +import type { FC } from 'hono/jsx' +import { cors } from 'hono/cors' +import type { Context as HonoContext } from 'hono' +import { serveStatic } from 'hono/deno' +import { APIClient } from '@d8d-appcontainer/api' +import debug from "debug" +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import type { SystemSettingRecord, GlobalConfig } from './asset/share/types.ts'; +import { SystemSettingKey, OssType, MapMode } from './asset/share/types.ts'; + +import { + createKnowInfoRoutes, + createFileCategoryRoutes, + createFileUploadRoutes, + createThemeRoutes, + createSystemSettingsRoutes, +} from "./routes_sys.ts"; + +import { + createMapRoutes, +} from "./routes_maps.ts"; + +import { + createChartRoutes, +} from "./routes_charts.ts"; + +import { migrations } from './migrations.ts'; +// 导入基础路由 +import { createAuthRoutes } from "./routes_auth.ts"; +import { createUserRoutes } from "./routes_users.ts"; + +dayjs.extend(utc) +// 初始化debug实例 +const log = { + app: debug('app:server'), + auth: debug('auth:server'), + api: debug('api:server'), + debug: debug('debug:server') +} + +const GLOBAL_CONFIG: GlobalConfig = { + OSS_BASE_URL: Deno.env.get('OSS_BASE_URL') || 'https://d8d-appcontainer-user.oss-cn-beijing.aliyuncs.com', + OSS_TYPE: Deno.env.get('OSS_TYPE') === OssType.MINIO ? OssType.MINIO : OssType.ALIYUN, + API_BASE_URL: '/api', + APP_NAME: Deno.env.get('APP_NAME') || '应用Starter', + ENV: Deno.env.get('ENV') || 'development', + DEFAULT_THEME: 'light', // 默认主题 + MAP_CONFIG: { + KEY: Deno.env.get('AMAP_KEY') || '您的地图API密钥', + VERSION: '2.0', + PLUGINS: ['AMap.ToolBar', 'AMap.Scale', 'AMap.HawkEye', 'AMap.MapType', 'AMap.Geolocation'], + MAP_MODE: Deno.env.get('MAP_MODE') === MapMode.OFFLINE ? MapMode.OFFLINE : MapMode.ONLINE, + }, + CHART_THEME: 'default', // 图表主题 + ENABLE_THEME_CONFIG: false, // 主题配置开关 + THEME: null +}; + +log.app.enabled = true +log.auth.enabled = true +log.api.enabled = true +log.debug.enabled = true + +// 定义自定义上下文类型 +export interface Variables { + auth: Auth + user?: AuthUser + apiClient: APIClient + moduleDir: string + systemSettings?: SystemSettingRecord +} + +// 定义登录历史类型 +interface LoginHistory { + id: number + user_id: number + login_time: string + ip_address?: string + user_agent?: string +} + +// 定义仪表盘数据类型 +interface DashboardData { + lastLogin: string + loginCount: number + fileCount: number + userCount: number + systemInfo: { + version: string + lastUpdate: string + } +} + +interface EsmScriptConfig { + src: string + href: string + denoJson: string + refresh: boolean + prodPath?: string + prodSrc?: string +} + +// Auth实例 +let authInstance: Auth | null = null + +// 初始化Auth实例 +const initAuth = async (apiClient: APIClient) => { + try { + if (authInstance) { + return authInstance + } + + log.auth('正在初始化Auth实例') + + authInstance = new Auth(apiClient as any, { + jwtSecret: Deno.env.get("JWT_SECRET") || 'your-jwt-secret-key', + initialUsers: [], + storagePrefix: '', + userTable: 'users', + fieldNames: { + id: 'id', + username: 'username', + password: 'password', + phone: 'phone', + email: 'email', + is_disabled: 'is_disabled', + is_deleted: 'is_deleted' + }, + tokenExpiry: 24 * 60 * 60, + refreshTokenExpiry: 7 * 24 * 60 * 60 + }) + + log.auth('Auth实例初始化完成') + + return authInstance + } catch (error) { + log.auth('Auth初始化失败:', error) + throw error + } +} + +// 初始化系统设置 +const initSystemSettings = async (apiClient: APIClient) => { + try { + const systemSettings = await apiClient.database.table('system_settings') + .select() + + // 将系统设置转换为键值对形式 + const settings = systemSettings.reduce((acc: Record, setting: any) => { + acc[setting.key] = setting.value + return acc + }, {}) as SystemSettingRecord + + // 更新全局配置 + if (settings[SystemSettingKey.SITE_NAME]) { + GLOBAL_CONFIG.APP_NAME = String(settings[SystemSettingKey.SITE_NAME]) + } + + // 设置其他全局配置项 + if (settings[SystemSettingKey.SITE_FAVICON]) { + GLOBAL_CONFIG.DEFAULT_THEME = String(settings[SystemSettingKey.SITE_FAVICON]) + } + + if (settings[SystemSettingKey.SITE_LOGO]) { + GLOBAL_CONFIG.MAP_CONFIG.KEY = String(settings[SystemSettingKey.SITE_LOGO]) + } + + if (settings[SystemSettingKey.SITE_DESCRIPTION]) { + GLOBAL_CONFIG.CHART_THEME = String(settings[SystemSettingKey.SITE_DESCRIPTION]) + } + + // 设置主题配置开关 + if (settings[SystemSettingKey.ENABLE_THEME_CONFIG]) { + GLOBAL_CONFIG.ENABLE_THEME_CONFIG = settings[SystemSettingKey.ENABLE_THEME_CONFIG] === 'true' + } + + // 查询ID1管理员的主题配置 + const adminTheme = await apiClient.database.table('theme_settings') + .where('user_id', 1) + .first() + + if (adminTheme) { + GLOBAL_CONFIG.THEME = adminTheme.settings + } + + return settings + + } catch (error) { + log.app('获取系统设置失败:', error) + return {} as SystemSettingRecord + } +} + +// 初始化数据库 +const initDatabase = async (apiClient: APIClient) => { + try { + log.app('正在执行数据库迁移...') + + const migrationsResult = await apiClient.database.executeLiveMigrations(migrations) + // log.app('数据库迁移完成 %O',migrationsResult) + log.app('数据库迁移完成') + + } catch (error) { + log.app('数据库迁移失败:', error) + } +} + +// 中间件:数据库初始化 +const withDatabase = async (c: HonoContext<{ Variables: Variables }>, next: () => Promise) => { + try { + const apiClient = c.get('apiClient') + await initDatabase(apiClient) + await next() + } catch (error) { + log.api('数据库操作失败:', error) + return c.json({ error: '数据库操作失败' }, 500) + } +} + +// 中间件:验证认证 +const withAuth = async (c: HonoContext<{ Variables: Variables }>, next: () => Promise) => { + try { + const auth = c.get('auth') + + const token = c.req.header('Authorization')?.replace('Bearer ', '') + if (token) { + const userData = await auth.verifyToken(token) + if (userData) { + c.set('user', userData) + await next() + return + } + } + + return c.json({ error: '未授权' }, 401) + } catch (error) { + log.auth('认证失败:', error) + return c.json({ error: '无效凭证' }, 401) + } +} + +// 导出withAuth类型定义 +export type WithAuth = typeof withAuth; + +// 定义模块参数接口 +interface ModuleParams { + apiClient: APIClient + app: Hono<{ Variables: Variables }> + moduleDir: string +} + +export default function({ apiClient, app, moduleDir }: ModuleParams) { + const honoApp = app + // 添加CORS中间件 + honoApp.use('/*', cors()) + + // 创建API路由 + const api = new Hono<{ Variables: Variables }>() + + // 设置环境变量 + api.use('*', async (c, next) => { + c.set('apiClient', apiClient) + c.set('moduleDir', moduleDir) + c.set('auth', await initAuth(apiClient)) + c.set('systemSettings', await initSystemSettings(apiClient)) + await next() + }) + + // 使用数据库中间件 + api.use('/', withDatabase) + + // 查询仪表盘数据 + api.get('/dashboard', withAuth, async (c) => { + try { + const user = c.get('user')! + const apiClient = c.get('apiClient') + const lastLogin = await apiClient.database.table('login_history') + .where('user_id', user.id) + .orderBy('login_time', 'desc') + .limit(1) + .first() + + // 获取登录总次数 + const loginCount = await apiClient.database.table('login_history') + .where('user_id', user.id) + .count() + + // 获取系统数据统计 + const fileCount = await apiClient.database.table('file_library') + .where('is_deleted', 0) + .count() + + const userCount = await apiClient.database.table('users') + .where('is_deleted', 0) + .count() + + // 返回仪表盘数据 + const dashboardData: DashboardData = { + lastLogin: lastLogin ? lastLogin.login_time : new Date().toISOString(), + loginCount: loginCount, + fileCount: Number(fileCount), + userCount: Number(userCount), + systemInfo: { + version: '1.0.0', + lastUpdate: new Date().toISOString() + } + } + + return c.json(dashboardData) + } catch (error) { + log.api('获取仪表盘数据失败:', error) + return c.json({ error: '获取仪表盘数据失败' }, 500) + } + }) + // 注册基础路由 + api.route('/auth', createAuthRoutes(withAuth)) + api.route('/users', createUserRoutes(withAuth)) + api.route('/know-info', createKnowInfoRoutes(withAuth)) + api.route('/upload', createFileUploadRoutes(withAuth)) // 添加文件上传路由 + api.route('/file-categories', createFileCategoryRoutes(withAuth)) // 添加文件分类管理路由 + api.route('/theme', createThemeRoutes(withAuth)) // 添加主题设置路由 + api.route('/charts', createChartRoutes(withAuth)) // 添加图表数据路由 + api.route('/map', createMapRoutes(withAuth)) // 添加地图数据路由 + api.route('/settings', createSystemSettingsRoutes(withAuth)) // 添加系统设置路由 + + // 注册API路由 + honoApp.route('/api', api) + + // 首页路由 - SSR + honoApp.get('/', async (c: HonoContext) => { + const systemName = GLOBAL_CONFIG.APP_NAME + return c.html( + + + {systemName} + + + + + +
+
+ {/* 系统介绍区域 */} +
+

+ {systemName} +

+

+ 全功能应用Starter +

+

+ 这是一个基于Hono和React的应用Starter,提供了用户认证、文件管理、图表分析、地图集成和主题切换等常用功能。 +

+
+ + {/* 管理入口按钮 */} + +
+
+ + + ) + }) + + // 创建一个函数,用于生成包含全局配置的HTML页面 + const createHtmlWithConfig = (scriptConfig: EsmScriptConfig, title = '应用Starter') => { + return (c: HonoContext) => { + const isProd = GLOBAL_CONFIG.ENV === 'production'; + + return c.html( + + + + + {title} + + {isProd ? ( + + ) : ( + + )} + + {isProd ? () : ()} + + +