fix: 全面 QA 审计修复 — 安全加固/代码质量/跨平台一致性/测试覆盖
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

Phase 0 安全热修复 (CRITICAL):
- 外部化微信 appid/secret 到 ERP__WECHAT__APPID/SECRET 环境变量
- 正确连接 HealthCrypto 到 ERP__HEALTH__AES_KEY/HMAC_KEY 环境变量
- 外部化小程序加密密钥到 TARO_APP_ENCRYPTION_KEY 环境变量
- 移除小程序 auth store 中的敏感信息 console.log

Phase 1 安全加固:
- 微信自动注册 display_name 添加 sanitize 防止 XSS
- 测试数据库凭据改为从 TEST_DB_URL 环境变量读取

Phase 2 代码质量:
- 提取 useThemeMode hook 消除 22 处重复暗色模式检测
- 提取共享健康常量到 constants/health.ts
- 拆分 patient_service.rs 脱敏函数到 masking.rs
- 移除未使用的 i18next/react-i18next 依赖
- 移除未使用的 api/errors.ts 和 erp-auth/anyhow 依赖

Phase 3 测试覆盖:
- 新增 5 个患者模块集成测试 (CRUD/租户隔离/验证/软删除)

Phase 4 跨平台一致性:
- 统一小程序 Patient.birthday → birth_date 匹配后端
- 统一小程序 Appointment.time_slot → start_time/end_time 匹配后端

Phase 5 架构:
- 微信登录添加多租户 TODO 注释
- 更新 wiki/infrastructure.md 环境变量文档
This commit is contained in:
iven
2026-04-25 10:00:49 +08:00
parent 07f4ba41ba
commit 945ccd64ba
56 changed files with 634 additions and 273 deletions

View File

@@ -1,8 +1,9 @@
import { useEffect, useState, useCallback } from 'react';
import { Table, Button, Modal, Form, Input, Select, message, theme, Tag } from 'antd';
import { Table, Button, Modal, Form, Input, Select, message, Tag } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import { listTemplates, createTemplate, type MessageTemplateInfo } from '../../api/messageTemplates';
import { useThemeMode } from '../../hooks/useThemeMode';
const channelMap: Record<string, { label: string; color: string }> = {
in_app: { label: '站内', color: '#2563eb' },
@@ -18,8 +19,7 @@ export default function MessageTemplates() {
const [loading, setLoading] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [form] = Form.useForm();
const { token } = theme.useToken();
const isDark = token.colorBgContainer === '#111827' || token.colorBgContainer === 'rgb(17, 24, 39)';
const isDark = useThemeMode();
const fetchData = useCallback(async (p = page) => {
setLoading(true);

View File

@@ -1,8 +1,9 @@
import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
import { Table, Button, Tag, Space, Modal, Typography, message, theme } from 'antd';
import { Table, Button, Tag, Space, Modal, Typography, message } from 'antd';
import { CheckOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import { listMessages, markRead, markAllRead, deleteMessage, type MessageInfo, type MessageQuery } from '../../api/messages';
import { useThemeMode } from '../../hooks/useThemeMode';
const { Paragraph } = Typography;
@@ -21,8 +22,7 @@ export default function NotificationList({ queryFilter }: Props) {
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const { token } = theme.useToken();
const isDark = token.colorBgContainer === '#111827' || token.colorBgContainer === 'rgb(17, 24, 39)';
const isDark = useThemeMode();
const fetchData = useCallback(async (p = page, filter?: MessageQuery) => {
setLoading(true);

View File

@@ -1,7 +1,8 @@
import { useEffect, useState } from 'react';
import { Form, Switch, TimePicker, Button, message, theme } from 'antd';
import { Form, Switch, TimePicker, Button, message } from 'antd';
import { BellOutlined } from '@ant-design/icons';
import client from '../../api/client';
import { useThemeMode } from '../../hooks/useThemeMode';
interface PreferencesData {
dnd_enabled: boolean;
@@ -13,8 +14,7 @@ export default function NotificationPreferences() {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [dndEnabled, setDndEnabled] = useState(false);
const { token } = theme.useToken();
const isDark = token.colorBgContainer === '#111827' || token.colorBgContainer === 'rgb(17, 24, 39)';
const isDark = useThemeMode();
useEffect(() => {
form.setFieldsValue({ dnd_enabled: false });