feat(framework): 回灌 OpenFlare 分层、安全与运行时改进

将平台域持久化收敛为 repository 唯一入口,model 去掉 IO。
邮件头写入前清除 CR/LF,防止 header 注入。
httppool 支持可配置 Transport;batchwriter 增加 MinBatchSize/Stats,flush 失败交回批次;任务 PermanentError 作为 SkipRetry 终态。
设置与推送页的确认改为 AlertDialog;axios 去尾斜杠并按 Gin 数组序列化查询参数。
升级共享 Go 依赖(Gin、Asynq、OTel、GORM、Redis 等)。
This commit is contained in:
ryan
2026-08-16 11:07:20 +08:00
parent b9b42e3174
commit 6a53619dd2
58 changed files with 2201 additions and 1175 deletions
@@ -43,6 +43,16 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import {
Select,
SelectContent,
@@ -74,6 +84,9 @@ import { PushService } from '@/lib/services/push';
export function EventsTab() {
const t = useTranslations('admin.push.events');
const queryClient = useQueryClient();
const [deleteTarget, setDeleteTarget] = React.useState<PushEvent | null>(
null,
);
// --- 获取所有自定义消息通道 ---
const channelsQuery = useQuery({
@@ -149,6 +162,7 @@ export function EventsTab() {
const deleteEventMutation = useMutation({
mutationFn: (id: number) => PushService.deleteEvent(id),
onSuccess: () => {
setDeleteTarget(null);
toast.success(t('configDeleteSuccess'));
queryClient.invalidateQueries({ queryKey: ['admin', 'push-events'] });
},
@@ -455,11 +469,7 @@ export function EventsTab() {
size='icon'
className='h-6 w-6 text-muted-foreground hover:text-destructive hover:bg-destructive/10'
disabled={deleteEventMutation.isPending}
onClick={() => {
if (confirm(t('deleteEventConfirm'))) {
deleteEventMutation.mutate(event.id);
}
}}
onClick={() => setDeleteTarget(event)}
>
<Trash2 className='size-3' />
</Button>
@@ -919,6 +929,35 @@ export function EventsTab() {
</DialogFooter>
</DialogContent>
</Dialog>
<AlertDialog
open={Boolean(deleteTarget)}
onOpenChange={(open) => !open && setDeleteTarget(null)}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t('deleteEventTitle')}</AlertDialogTitle>
<AlertDialogDescription>
{t('deleteEventConfirm')}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={deleteEventMutation.isPending}>
{t('cancel')}
</AlertDialogCancel>
<AlertDialogAction
disabled={deleteEventMutation.isPending}
onClick={() =>
deleteTarget && deleteEventMutation.mutate(deleteTarget.id)
}
>
{deleteEventMutation.isPending
? t('deleting')
: t('confirmDelete')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}
@@ -32,6 +32,16 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import {
Select,
SelectContent,
@@ -54,6 +64,9 @@ import { PushService } from '@/lib/services/push';
export function SettingsTab() {
const t = useTranslations('admin.push.settings');
const queryClient = useQueryClient();
const [deleteTarget, setDeleteTarget] = React.useState<PushChannel | null>(
null,
);
// --- 获取所有自定义消息通道 ---
const channelsQuery = useQuery({
@@ -96,6 +109,7 @@ export function SettingsTab() {
const deleteChannelMutation = useMutation({
mutationFn: (id: number) => PushService.deleteChannel(id),
onSuccess: () => {
setDeleteTarget(null);
toast.success(t('channelDeleteSuccess'));
queryClient.invalidateQueries({ queryKey: ['admin', 'push-channels'] });
},
@@ -419,15 +433,7 @@ export function SettingsTab() {
variant='ghost'
size='sm'
disabled={deleteChannelMutation.isPending}
onClick={() => {
if (
confirm(
t('deleteChannelConfirm', { name: ch.name }),
)
) {
deleteChannelMutation.mutate(ch.id);
}
}}
onClick={() => setDeleteTarget(ch)}
className='h-6 px-2 text-[10px] text-destructive hover:text-destructive hover:bg-destructive/10'
>
<Trash2 className='size-2.5 mr-1' />
@@ -772,6 +778,35 @@ export function SettingsTab() {
</DialogFooter>
</DialogContent>
</Dialog>
<AlertDialog
open={Boolean(deleteTarget)}
onOpenChange={(open) => !open && setDeleteTarget(null)}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t('deleteChannelTitle')}</AlertDialogTitle>
<AlertDialogDescription>
{t('deleteChannelConfirm', { name: deleteTarget?.name ?? '' })}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={deleteChannelMutation.isPending}>
{t('cancel')}
</AlertDialogCancel>
<AlertDialogAction
disabled={deleteChannelMutation.isPending}
onClick={() =>
deleteTarget && deleteChannelMutation.mutate(deleteTarget.id)
}
>
{deleteChannelMutation.isPending
? t('deleting')
: t('confirmDelete')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}