跳到内容
当前位置:首页>Excel>spring-boot-excel-import-sse-progress

spring-boot-excel-import-sse-progress

  • 2026-09-22 01:55:10
spring-boot-excel-import-sse-progress

前段时间改了一个后台的 Excel 导入功能。

最开始这个功能其实很普通。

运营上传一个 Excel,里面是商品数据,后端解析以后写进 MySQL。

数据少的时候完全没问题。

几百条:

1 秒左右

几千条:

几秒

后来运营拿了一份 10 万多行的 Excel 上来。

页面点完“导入”以后,一直转圈。

转了几十秒。

最后浏览器直接提示:

504 Gateway Timeout

运营又点了一次。

结果更麻烦。

第一批任务其实还在后台跑,第二批数据又开始导。

数据库里出现了一堆重复数据。

我去看代码,发现原来的实现大概是这样的:

@PostMapping(”/import”)public ImportResult importExcel(        @RequestParam(”file”) MultipartFile file)        throws Exception {    List rows =            excelReader.read(                    file.getInputStream()            );    List products =            rows.stream()                    .map(this::convert)                    .toList();    productRepository.saveAll(products);    return ImportResult.success(            products.size()    );}

几千条的时候,这段代码一点毛病都看不出来。

但数据一到十万级,几个问题同时出来了。

Excel 全部读进内存。

10 万个 Java 对象留在一个 List 里。

HTTP 请求一直等数据库处理完成。

某一行数据错误,整批怎么处理也没想清楚。

用户根本不知道:

到底导到哪了?

更麻烦的是,只要网关超时,不代表后端任务真的停止。

用户看到的是:

导入失败

服务器实际上可能还在:

INSERTINSERTINSERT

这才是这类接口真正危险的地方。

后来我没有继续优化这个 Controller。

而是直接把整个模型换掉了。

现在上传 Excel 以后,接口不会负责完成导入。

它只做三件事:

保存上传文件创建导入任务返回 taskId

真正解析 Excel 和写数据库,在后台慢慢处理。

于是一个 Excel 导入从:

HTTP 请求   ↓解析 10 万行   ↓校验   ↓写数据库   ↓返回结果

变成了:

HTTP 请求   ↓保存文件   ↓创建任务   ↓立即返回 taskId后台任务   ↓流式解析   ↓逐行校验   ↓500 条一批入库   ↓更新进度   ↓SSE 实时推送进度   ↓记录失败数据

这个改动做完以后,Controller 反而非常简单。

我先建了一张导入任务表。

CREATE TABLE import_task (    id VARCHAR(64) PRIMARY KEY,    file_name VARCHAR(255) NOT NULL,    file_path VARCHAR(512) NOT NULL,    status VARCHAR(32) NOT NULL,    processed_rows BIGINT NOT NULL DEFAULT 0,    success_rows BIGINT NOT NULL DEFAULT 0,    failed_rows BIGINT NOT NULL DEFAULT 0,    error_message VARCHAR(1000),    created_at DATETIME NOT NULL,    started_at DATETIME,    finished_at DATETIME);

状态我只用了几个:

WAITINGRUNNINGSUCCESSPARTIAL_SUCCESSFAILED

然后上传接口改成这样:

@PostMapping(”/api/imports”)public ImportTaskResponse upload(        @RequestParam(”file”)        MultipartFile file)        throws IOException {    String taskId =            UUID.randomUUID().toString();    Path filePath =            importFileStore.save(                    taskId,                    file            );    importTaskService.create(            taskId,            file.getOriginalFilename(),            filePath.toString()    );    importTaskExecutor.execute(            () -> importProcessor.process(                    taskId,                    filePath            )    );    return new ImportTaskResponse(            taskId,            ”WAITING”    );}

这里有一个坑我以前也踩过。

千万不要这样:

importTaskExecutor.execute(        () -> {            file.getInputStream();        });

也就是:

Controller 收到 MultipartFile直接把 MultipartFile 扔给异步线程

看起来没问题。

但 MultipartFile 本质上属于当前 HTTP 请求。

请求结束以后,Servlet 容器用于保存上传内容的临时资源可能已经被清理。

所以我现在第一步一定是:

Files.copy(...)

把文件保存到我们自己控制的目录。

例如:

/data/import/    7c8290....xlsx

请求结束以后,这个文件还在。

后台任务什么时候处理都没有关系。

文件保存逻辑很简单:

@Componentpublic class ImportFileStore {    private final Path root =            Paths.get(”/data/import”);    public Path save(            String taskId,            MultipartFile file)            throws IOException {        Files.createDirectories(root);        Path target =                root.resolve(                        taskId + ”.xlsx”                );        try (InputStream input =                     file.getInputStream()) {            Files.copy(                    input,                    target,                    StandardCopyOption                            .REPLACE_EXISTING            );        }        return target;    }}

异步线程池我也没有直接用默认的 @Async,而是专门给 Excel 导入建了一个线程池。

@Configurationpublic class ImportExecutorConfig {    @Bean    public ThreadPoolTaskExecutor            importTaskExecutor() {        ThreadPoolTaskExecutor executor =                new ThreadPoolTaskExecutor();        executor.setCorePoolSize(2);        executor.setMaxPoolSize(4);        executor.setQueueCapacity(20);        executor.setThreadNamePrefix(                ”excel-import-”        );        executor.initialize();        return executor;    }}

Excel 导入本身就是:

文件 IO+数据校验+数据库写入

开几十个任务同时导,并不会让一份 Excel 更快。

反而很容易把数据库连接池吃满。

所以这种后台批处理任务,我宁愿:

少量并发+排队

也不会无限开线程。

接下来就是 Excel 怎么读。

这里我这次没有继续照很多旧文章里的写法直接引 EasyExcel。

原因很现实:Alibaba EasyExcel 的 GitHub 仓库已经在 2025 年 9 月归档并进入只读状态,老项目如果已经稳定使用当然没必要马上替换,但新项目我更愿意选仍在持续维护的 Apache POI。

依赖:

org.apache.poi    poi-ooxml    5.5.1

以前不少代码是这样:

try (Workbook workbook =             WorkbookFactory.create(file)) {    Sheet sheet =            workbook.getSheetAt(0);    for (Row row : sheet) {        ...    }}

小文件这么写没什么问题。

但大文件我现在会直接走 Event 模式。

核心区别是:

Workbook 模型:把大量 Excel 对象构造成 Java 对象然后遍历SAX/Event:读到一行处理一行然后丢掉

也就是说,10 万行并不意味着 JVM 里同时存在 10 万个 ProductImportRow。

我只保留当前这一批。

我最终做了一个流式解析器:

@Componentpublic class ExcelStreamReader {    public void read(            Path file,            Consumer consumer)            throws Exception {        try (OPCPackage pkg =                     OPCPackage.open(                             file.toFile(),                             PackageAccess.READ                     )) {            ReadOnlySharedStringsTable strings =                    new ReadOnlySharedStringsTable(pkg);            XSSFReader reader =                    new XSSFReader(pkg);            StylesTable styles =                    reader.getStylesTable();            DataFormatter formatter =                    new DataFormatter();            Iterator sheets =                    reader.getSheetsData();            if (!sheets.hasNext()) {                return;            }            try (InputStream sheet =                         sheets.next()) {                XMLReader parser =                        XMLHelper.newXMLReader();                ProductSheetHandler handler =                        new ProductSheetHandler(                                consumer                        );                ContentHandler contentHandler =                        new XSSFSheetXMLHandler(                                styles,                                null,                                strings,                                handler,                                formatter,                                false                        );                parser.setContentHandler(                        contentHandler                );                parser.parse(                        new InputSource(sheet)                );            }        }    }}

真正收到一行数据的是 ProductSheetHandler:

public class ProductSheetHandler        implements XSSFSheetXMLHandler        .SheetContentsHandler {    private final Consumer            consumer;    private final Map cells =            new HashMap<>();    public ProductSheetHandler(            Consumer consumer) {        this.consumer = consumer;    }    @Override    public void startRow(int rowNum) {        cells.clear();    }    @Override    public void cell(            String cellReference,            String formattedValue,            XSSFComment comment) {        int column =                new CellReference(                        cellReference                ).getCol();        cells.put(                column,                formattedValue        );    }    @Override    public void endRow(int rowNum) {        if (rowNum == 0) {            return;        }        ProductImportRow row =                new ProductImportRow(                        rowNum + 1,                        cells.get(0),                        cells.get(1),                        cells.get(2),                        cells.get(3)                );        consumer.accept(row);    }}

这里比较关键的不是 POI 这几个类怎么写。

真正重要的是:

consumer.accept(row);

每解析一行就交出去。

而不是:

List rows =        new ArrayList<>();rows.add(...);

最后让 10 万、20 万甚至 50 万个对象一直留在内存里。

真正的导入逻辑,我只保留 500 条。

@Componentpublic class ImportProcessor {    private static final int BATCH_SIZE = 500;    private final ExcelStreamReader excelReader;    private final ProductBatchWriter batchWriter;    private final ImportTaskService taskService;    private final ImportErrorService errorService;    private final ImportProgressPublisher publisher;    public void process(            String taskId,            Path file) {        taskService.markRunning(taskId);        List batch =                new ArrayList<>(BATCH_SIZE);        AtomicLong processed =                new AtomicLong();        AtomicLong success =                new AtomicLong();        AtomicLong failed =                new AtomicLong();        try {            excelReader.read(                    file,                    row -> {                        long current =                                processed                                        .incrementAndGet();                        try {                            Product product =                                    validateAndConvert(                                            row                                    );                            batch.add(product);                        } catch (Exception e) {                            failed.incrementAndGet();                            errorService.save(                                    taskId,                                    row.rowNo(),                                    row,                                    e.getMessage()                            );                        }                        if (batch.size()                                >= BATCH_SIZE) {                            int count =                                    batchWriter                                            .write(batch);                            success.addAndGet(                                    count                            );                            batch.clear();                        }                        if (current % 500 == 0) {                            taskService                                    .updateProgress(                                            taskId,                                            processed.get(),                                            success.get(),                                            failed.get()                                    );                            publisher.publish(                                    taskId,                                    processed.get(),                                    success.get(),                                    failed.get()                            );                        }                    }            );            if (!batch.isEmpty()) {                int count =                        batchWriter.write(                                batch                        );                success.addAndGet(count);                batch.clear();            }            taskService.finish(                    taskId,                    processed.get(),                    success.get(),                    failed.get()            );            publisher.complete(taskId);        } catch (Exception e) {            taskService.fail(                    taskId,                    e.getMessage()            );            publisher.error(                    taskId,                    e.getMessage()            );        } finally {            try {                Files.deleteIfExists(file);            } catch (IOException ignored) {            }        }    }}

这样无论 Excel 是 1 万行还是 30 万行,业务层真正长期持有的 List 大概都是 500 条,而不是整个 Excel。

数据库这里我也没有再写:

for (Product product : products) {    repository.save(product);}

直接使用 JDBC batch。

@Repositorypublic class ProductBatchWriter {    private final JdbcTemplate jdbcTemplate;    public ProductBatchWriter(            JdbcTemplate jdbcTemplate) {        this.jdbcTemplate =                jdbcTemplate;    }    public int write(            List products) {        int[][] result =                jdbcTemplate.batchUpdate(                        ”””                        INSERT INTO product(                            sku,                            name,                            price,                            stock                        )                        VALUES (?, ?, ?, ?)                        ”””,                        products,                        products.size(),                        (ps, product) -> {                            ps.setString(                                    1,                                    product.sku()                            );                            ps.setString(                                    2,                                    product.name()                            );                            ps.setBigDecimal(                                    3,                                    product.price()                            );                            ps.setInt(                                    4,                                    product.stock()                            );                        }                );        return Arrays.stream(result)                .mapToInt(                        batch ->                                (int) Arrays                                        .stream(batch)                                        .filter(                                                value ->                                                        value >= 0                                        )                                        .count()                )                .sum();    }}

200、500、1000 都可以测。

真正不能做的是:

解析一行INSERT 一次

10 万条就是十万次数据库交互。

这时候 Excel 解析可能根本不是瓶颈。

数据库往返才是。

另外一个以前一直让我觉得很别扭的问题,是错误数据。

假设 10 万行里面:

99,872 条正确128 条错误

以前很多导入代码的处理方式是:

第 38127 行价格为空throw Exception整份 Excel 导入失败

其实没必要。

现在我把“行级错误”和“任务级错误”分开了。

比如:

SKU 为空价格格式错误库存小于 0手机号格式不正确

这些属于行级问题。

这一行失败:

记录错误继续下一行

只有:

Excel 文件损坏数据库不可用磁盘读取失败表头完全不匹配

这种问题,我才让整个任务进入 FAILED。

错误数据单独写表:

CREATE TABLE import_error (    id BIGINT PRIMARY KEY AUTO_INCREMENT,    task_id VARCHAR(64) NOT NULL,    row_no BIGINT NOT NULL,    raw_data TEXT,    error_message VARCHAR(1000),    created_at DATETIME NOT NULL,    INDEX idx_task_id(task_id));

最终任务结果可能是:

任务状态:PARTIAL_SUCCESS总处理:100000成功:99872失败:128

后台再给一个“下载失败数据”,运营改这 128 条就行。

不用重新处理十万条。

做到这里其实已经能用了。

但我后来又加了一点东西,用户体验好了很多:

SSE 实时进度。

以前上传以后只能:

正在导入,请稍候……

到底还有一分钟还是十分钟,谁也不知道。

这种场景我没有上 WebSocket。

因为浏览器只需要:

服务器 → 浏览器

单向推送。

直接用 SSE 就够了。

Spring MVC 自带 SseEmitter,很适合这种“后台任务不断把进度推给前端”的场景。

我写了一个非常简单的 Publisher:

@Componentpublic class ImportProgressPublisher {    private final Map<            String,            List            > emitters =            new ConcurrentHashMap<>();    public SseEmitter subscribe(            String taskId) {        SseEmitter emitter =                new SseEmitter(                        30L * 60 * 1000                );        emitters.computeIfAbsent(                taskId,                key ->                        new CopyOnWriteArrayList<>()        ).add(emitter);        emitter.onCompletion(                () -> remove(                        taskId,                        emitter                )        );        emitter.onTimeout(                () -> remove(                        taskId,                        emitter                )        );        return emitter;    }    public void publish(            String taskId,            long processed,            long success,            long failed) {        ImportProgress progress =                new ImportProgress(                        processed,                        success,                        failed                );        List list =                emitters.getOrDefault(                        taskId,                        List.of()                );        for (SseEmitter emitter : list) {            try {                emitter.send(                        SseEmitter                                .event()                                .name(”progress”)                                .data(progress)                );            } catch (IOException e) {                remove(                        taskId,                        emitter                );            }        }    }    public void complete(String taskId) {        List list =                emitters.remove(taskId);        if (list == null) {            return;        }        for (SseEmitter emitter : list) {            try {                emitter.send(                        SseEmitter                                .event()                                .name(”complete”)                                .data(”SUCCESS”)                );                emitter.complete();            } catch (IOException ignored) {            }        }    }    public void error(            String taskId,            String message) {        List list =                emitters.remove(taskId);        if (list == null) {            return;        }        for (SseEmitter emitter : list) {            try {                emitter.send(                        SseEmitter                                .event()                                .name(”error”)                                .data(message)                );                emitter.complete();            } catch (IOException ignored) {            }        }    }    private void remove(            String taskId,            SseEmitter emitter) {        List list =                emitters.get(taskId);        if (list != null) {            list.remove(emitter);        }    }}

Controller:

@GetMapping(        value = ”/api/imports/{taskId}/events”,        produces =                MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter events(        @PathVariable String taskId) {    return publisher.subscribe(            taskId    );}

浏览器:

const source =    new EventSource(        `/api/imports/${taskId}/events`    );source.addEventListener(    ”progress”,    event => {        const data =            JSON.parse(event.data);        console.log(            ”已处理:”,            data.processed        );        console.log(            ”成功:”,            data.success        );        console.log(            ”失败:”,            data.failed        );    });source.addEventListener(    ”complete”,    event => {        console.log(”导入完成”);        source.close();    });source.addEventListener(    ”error”,    event => {        console.log(            ”导入失败”,            event.data        );        source.close();    });

于是页面可以直接显示:

正在导入……已处理:63,500成功:63,421失败:79

这比一个一直转圈的 Loading 好太多了。

而且 SSE 对这种场景特别合适。

因为我们根本不需要浏览器不停给服务器发送消息。

整个数据流就是:

后台任务   ↓Spring Boot   ↓SSE   ↓浏览器

单向推送就够了。

不过上面这个:

ConcurrentHashMap>

只适合单实例或者简单项目。

如果生产环境是:

Spring Boot ASpring Boot BSpring Boot C

上传请求到了 A。

SSE 连接却到了 B。

B 内存里根本没有 A 那个进度。

这时候我不会再硬改这个 Map。

真正的任务状态本来就在数据库:

processed_rowssuccess_rowsfailed_rowsstatus

浏览器刷新页面以后先:

GET /api/imports/{taskId}

查询持久化状态。

实时通知如果需要跨节点,再把 progress event 放到 Redis Pub/Sub、MQ 或其他共享通道。

这样数据库负责最终可信状态,SSE 只负责让页面看起来实时。

这两个职责不要反过来。

做完以后,我还顺手解决了另外一个以前很烦的问题。

用户刷新页面。

以前刷新以后:

导入进度没了。

现在浏览器手里有 taskId。

重新请求:

GET /api/imports/{taskId}

就能拿到:

{  ”taskId”: ”7c829...”,  ”status”: ”RUNNING”,  ”processedRows”: 63500,  ”successRows”: 63421,  ”failedRows”: 79}

然后重新订阅 SSE。

所以现在页面刷新、关掉甚至换一台电脑,都不会影响真正的导入任务。

还有一点需要特别注意。

SSE 本身并不是任务状态存储。

如果你只把:

635006342179

放在 JVM 内存里。

服务一重启,全部没了。

所以真正的状态还是要定期写数据库。

比如每处理 500 行更新一次:

UPDATE import_taskSET processed_rows = ?,    success_rows = ?,    failed_rows = ?WHERE id = ?;

然后 SSE 推送同样的数据。

这意味着哪怕:

浏览器断线Spring Boot 重启SSE 连接失效

用户重新打开页面以后,依然可以从数据库恢复到最近一次持久化的进度。

SSE 是实时体验。

数据库才是任务事实。

回过头来看,这次改造最重要的其实不是 POI 比以前省了多少内存,也不是 500 条 batch 到底比 1000 条快多少,甚至也不只是加了 SSE。

而是我不再把:

一个需要几分钟完成的后台任务

伪装成:

一个普通 HTTP 请求。

这是很多后台系统里特别容易出现的问题。

Excel 导入。

批量生成报表。

批量发送消息。

几十万条数据同步。

批量图片处理。

AI 批量生成。

只要任务可能跑:

几十秒几分钟甚至十几分钟

我现在第一反应已经不是:

把 nginx timeout 调成 600 秒。

而是:

是不是应该给它一个 taskId?

HTTP 请求负责提交任务。

后台线程负责执行任务。

数据库负责保存状态。

SSE 负责实时告诉用户现在跑到哪了。

失败数据单独记录:

能成功的继续成功。

而不是第 38,127 行出了一点问题,就让前面三万多条全部陪着重来。

我现在再看最开始那段代码:

@PostMapping(”/import”)public void importExcel(        MultipartFile file) {    List rows = read(file);    repository.saveAll(rows);}

它并不是错。

如果永远只有:

500 条

我甚至还是会这么写。

真正的问题是,我们经常拿一个原本为几百条数据设计的接口,慢慢承接:

50002000050000100000

最后出了问题以后,再不停加:

-Xmx4g
proxy_read_timeout 600
spring.mvc.async.request-timeout

但这些参数只是让问题晚一点出现。

如果一件事情本质上已经是:

后台批处理任务

最有效的优化,通常不是把 HTTP 超时时间从 30 秒改成 10 分钟。

而是别再让这个 HTTP 请求等它十分钟。

让请求快速返回一个:

taskId

让任务在后台跑。

再让 SSE 把:

已处理多少成功多少失败多少是否完成

实时推给页面。

这样才是真正适合生产环境的大批量 Excel 导入。

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-09-22 19:04:43 HTTP/1.1 GET : http://h.sjds.net/a/557244.html
  2. 运行时间 : 0.094620s [ 吞吐率:10.57req/s ] 内存消耗:4,369.91kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=96b3e54cbb627629f6880620bc2edb74
  1. /www/wwwroot/h.sjds.net/public/index.php ( 0.79 KB )
  2. /www/wwwroot/h.sjds.net/vendor/autoload.php ( 0.17 KB )
  3. /www/wwwroot/h.sjds.net/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /www/wwwroot/h.sjds.net/vendor/composer/platform_check.php ( 0.90 KB )
  5. /www/wwwroot/h.sjds.net/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /www/wwwroot/h.sjds.net/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /www/wwwroot/h.sjds.net/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /www/wwwroot/h.sjds.net/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /www/wwwroot/h.sjds.net/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /www/wwwroot/h.sjds.net/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /www/wwwroot/h.sjds.net/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /www/wwwroot/h.sjds.net/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /www/wwwroot/h.sjds.net/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /www/wwwroot/h.sjds.net/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /www/wwwroot/h.sjds.net/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /www/wwwroot/h.sjds.net/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /www/wwwroot/h.sjds.net/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /www/wwwroot/h.sjds.net/app/provider.php ( 0.19 KB )
  23. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /www/wwwroot/h.sjds.net/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /www/wwwroot/h.sjds.net/app/common.php ( 0.03 KB )
  27. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /www/wwwroot/h.sjds.net/config/app.php ( 0.95 KB )
  30. /www/wwwroot/h.sjds.net/config/cache.php ( 0.78 KB )
  31. /www/wwwroot/h.sjds.net/config/console.php ( 0.23 KB )
  32. /www/wwwroot/h.sjds.net/config/cookie.php ( 0.56 KB )
  33. /www/wwwroot/h.sjds.net/config/database.php ( 2.48 KB )
  34. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /www/wwwroot/h.sjds.net/config/filesystem.php ( 0.61 KB )
  36. /www/wwwroot/h.sjds.net/config/lang.php ( 0.91 KB )
  37. /www/wwwroot/h.sjds.net/config/log.php ( 1.35 KB )
  38. /www/wwwroot/h.sjds.net/config/middleware.php ( 0.19 KB )
  39. /www/wwwroot/h.sjds.net/config/route.php ( 1.89 KB )
  40. /www/wwwroot/h.sjds.net/config/session.php ( 0.57 KB )
  41. /www/wwwroot/h.sjds.net/config/trace.php ( 0.34 KB )
  42. /www/wwwroot/h.sjds.net/config/view.php ( 0.82 KB )
  43. /www/wwwroot/h.sjds.net/app/event.php ( 0.25 KB )
  44. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /www/wwwroot/h.sjds.net/app/service.php ( 0.13 KB )
  46. /www/wwwroot/h.sjds.net/app/AppService.php ( 0.26 KB )
  47. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /www/wwwroot/h.sjds.net/vendor/services.php ( 0.14 KB )
  53. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /www/wwwroot/h.sjds.net/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /www/wwwroot/h.sjds.net/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /www/wwwroot/h.sjds.net/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /www/wwwroot/h.sjds.net/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /www/wwwroot/h.sjds.net/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /www/wwwroot/h.sjds.net/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /www/wwwroot/h.sjds.net/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /www/wwwroot/h.sjds.net/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /www/wwwroot/h.sjds.net/app/Request.php ( 0.09 KB )
  84. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /www/wwwroot/h.sjds.net/app/middleware.php ( 0.25 KB )
  86. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /www/wwwroot/h.sjds.net/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /www/wwwroot/h.sjds.net/route/app.php ( 1.72 KB )
  100. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /www/wwwroot/h.sjds.net/app/controller/Index.php ( 4.81 KB )
  104. /www/wwwroot/h.sjds.net/app/BaseController.php ( 2.05 KB )
  105. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /www/wwwroot/h.sjds.net/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /www/wwwroot/h.sjds.net/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /www/wwwroot/h.sjds.net/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /www/wwwroot/h.sjds.net/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /www/wwwroot/h.sjds.net/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /www/wwwroot/h.sjds.net/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /www/wwwroot/h.sjds.net/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /www/wwwroot/h.sjds.net/runtime/temp/c1d6e8c94b2c2fa10e259903674c804d.php ( 8.11 KB )
  140. /www/wwwroot/h.sjds.net/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000636s ] mysql:host=172.18.0.4;port=3306;dbname=h_sjds;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000806s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000421s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000365s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000641s ]
  6. SELECT * FROM `set` [ RunTime:0.000305s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000691s ]
  8. SELECT * FROM `article` WHERE `id` = 557244 LIMIT 1 [ RunTime:0.000702s ]
  9. UPDATE `article` SET `lasttime` = 1790075083 WHERE `id` = 557244 [ RunTime:0.001903s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 65 LIMIT 1 [ RunTime:0.000335s ]
  11. SELECT * FROM `article` WHERE `id` < 557244 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000411s ]
  12. SELECT * FROM `article` WHERE `id` > 557244 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000364s ]
  13. SELECT * FROM `article` WHERE `id` < 557244 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000525s ]
  14. SELECT * FROM `article` WHERE `id` < 557244 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.000499s ]
  15. SELECT * FROM `article` WHERE `id` < 557244 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.000642s ]
0.106454s