刘光辉
4 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
package jnpf.dmsService;
 
import jnpf.dmsIntegration.audit.DmsBusinessChangeAudit;
import static jnpf.dmsIntegration.audit.DmsBusinessChangeAudit.values;
 
import jnpf.base.UserInfo;
import jnpf.dmsEntity.fenfa.*;
import jnpf.dmsEntity.fenfa.DmsWenjianDayinFasongEntity.Batch;
import jnpf.dmsEntity.fenfa.form.DmsWenjianChongdaForm;
import jnpf.dmsMapper.*;
import jnpf.dmsPermission.DmsCurrentIdentity;
import jnpf.exception.DataException;
import jnpf.util.JsonUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
 
@Service
@RequiredArgsConstructor
public class DmsWenjianChongdaService {
    private final DmsBusinessChangeAudit businessAudit;
    private final DmsCurrentIdentity identity;
    private final DmsWenjianFenfaMapper taskMapper;
    private final DmsWenjianDayinQuerenMapper versions;
    private final DmsWenjianChongdaMapper mapper;
    private final DmsWenjianBupinghengMapper documents;
    private final DmsFileVersionMapper fileVersions;
    private final DmsPermissionDecisionService permissions;
 
    /** 可展示给同租户用户的占用摘要,不返回可补回执的令牌和原签名。 */
    @Transactional(rollbackFor = Exception.class)
    public jnpf.dmsEntity.fenfa.vo.DmsWenjianChongdaLockVO lockStatus(String recordId) throws DataException {
        UserInfo actor = identity.requireUser();
        String tenant = identity.tenantId(actor);
        if (taskMapper.selectPrintForUpdate(tenant, recordId, null) == null)
            throw new DataException("打印记录不存在或已删除");
        var summary = new jnpf.dmsEntity.fenfa.vo.DmsWenjianChongdaLockVO();
        summary.setPrintRecordId(recordId);
        var attempt = mapper.selectActive(tenant, recordId);
        if (attempt == null) return summary;
        summary.setLocked(true); summary.setRequestId(attempt.getId());
        summary.setUserId(attempt.getUserId()); summary.setCreatedAt(attempt.getCreatedAt());
        summary.setReason(attempt.getReason());
        var user = mapper.selectActor(tenant, attempt.getUserId());
        summary.setUserName(user == null || blank(user.getUserName()) ? attempt.getUserId() : user.getUserName());
        boolean owner = Objects.equals(actor.getUserId(), attempt.getUserId());
        summary.setOwner(owner);
        List<Batch> list = batches(attempt);
        boolean unknown = list.stream().anyMatch(b -> "SENDING".equals(b.getState()));
        int submitted = (int) list.stream().filter(b -> "SUBMITTED".equals(b.getState())).count();
        summary.setSubmittedBatches(submitted); summary.setTotalBatches(list.size());
        summary.setPhase("CONFIRMED".equals(attempt.getState()) ? "REGISTRATION_PENDING"
                : unknown ? "UNKNOWN" : submitted > 0 ? "PARTIAL" : "READY");
        summary.setCanCancel(owner && "OPEN".equals(attempt.getState()) && neverDispatched(list));
        summary.setCanResume(owner && !unknown);
        return summary;
    }
 
    /** 取消与begin共用副本行锁;只允许尚未取得任何派发许可的任务。 */
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianChongdaFasong cancel(DmsWenjianChongdaForm form) throws DataException {
        var attempt = lockAttempt(form);
        Map<String, Object> before = controlSnapshot(attempt);
        if ("CANCELLED".equals(attempt.getState())) return attempt;
        if (!"OPEN".equals(attempt.getState()) || !neverDispatched(batches(attempt)))
            throw new DataException("任务已派发或结果未知,不能取消解锁;发送成功后请恢复登记");
        attempt.setState("CANCELLED"); attempt.setCancelledAt(new Date()); saveAttempt(attempt);
        recordControl("DMS_REPRINT_CANCEL", "重新打印取消", attempt, before);
        return attempt;
    }
 
    private boolean neverDispatched(List<Batch> list) {
        return list.stream().allMatch(b -> "READY".equals(b.getState()) && blank(b.getToken()));
    }
 
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianChongdaFasong active(String recordId) throws DataException {
        UserInfo actor = identity.requireUser();
        String tenant = identity.tenantId(actor);
        if (taskMapper.selectPrintForUpdate(tenant, recordId, null) == null)
            throw new DataException("打印记录不存在或已删除");
        DmsWenjianChongdaFasong attempt = mapper.selectActive(tenant, recordId);
        if (attempt != null) requireOwner(attempt, actor);
        return attempt;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianChongdaFasong prepare(DmsWenjianChongdaForm form) throws DataException {
        if (form == null || blank(form.getPrintRecordId())) throw new DataException("原打印记录不能为空");
        requireUuid(form.getRequestId());
        UserInfo actor = identity.requireUser();
        String tenant = identity.tenantId(actor);
        DmsWenjianDayinJiluEntity copy = taskMapper.selectPrintForUpdate(tenant, form.getPrintRecordId(), null);
        if (copy == null) throw new DataException("原打印记录不存在或已删除");
        DmsWenjianChongdaFasong prior = mapper.selectAttempt(tenant, form.getRequestId());
        if (prior != null) {
            requireOwner(prior, actor);
            if (!Objects.equals(prior.getPrintRecordId(), copy.getId())) throw new DataException("重打关联不能变更");
            if ("CANCELLED".equals(prior.getState())) throw new DataException("本次重打已取消,请关闭弹窗重新发起");
            return prior;
        }
        DmsWenjianChongdaFasong active = mapper.selectActive(tenant, copy.getId());
        if (active != null) {
            requireOwner(active, actor);
            throw new DataException("该副本存在未完成重打,请关闭弹窗并从原记录恢复");
        }
        eligible(copy);
        if (blank(form.getReason()) || form.getReason().trim().length() > 255 || blank(form.getBizSign()))
            throw new DataException("请填写255字以内重打原因并完成电子签名");
        DmsWenjianDayinQuerenEntity version = versions.selectTaskVersion(tenant, copy.getDayinRenwuId());
        if (version == null || blank(version.getFileVersionId()) || blank(copy.getDayinLiushuihao()))
            throw new DataException("原文件版本或流水缺失,不能重打");
        requirePrintPermission(tenant, version.getFileVersionId());
        if (mapper.validSignature(tenant, actor.getUserId(), form.getBizSign(), copy.getId(), form.getRequestId(), form.getReason().trim()) == null)
            throw new DataException("重打签名无效或不属于当前操作人");
        DmsWenjianChongdaFasong attempt = new DmsWenjianChongdaFasong();
        attempt.setId(form.getRequestId()); attempt.setTenantId(tenant); attempt.setPrintRecordId(copy.getId());
        attempt.setTaskId(copy.getDayinRenwuId()); attempt.setFileVersionId(version.getFileVersionId());
        attempt.setUserId(actor.getUserId()); attempt.setCopies(1); attempt.setState("OPEN");
        attempt.setBatchesJson("[]"); attempt.setReason(form.getReason().trim());
        attempt.setBizSign(form.getBizSign()); attempt.setCreatedAt(new Date());
        DmsWenjianDayinHaoma number = new DmsWenjianDayinHaoma();
        number.setSerialNumber(copy.getDayinLiushuihao()); number.setCountSerialNumber(copy.getDayinJishuLiushuihao());
        attempt.setNumbersJson(JsonUtil.getObjectToString(Collections.singletonList(number)));
        if (mapper.insertAttempt(attempt) != 1) throw new DataException("建立重打发送任务失败");
        return attempt;
    }
 
    /** 查询原生保存结果也用于响应丢失恢复,不创建或修改业务单据。 */
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianChongdaFasong status(DmsWenjianChongdaForm form) throws DataException {
        DmsWenjianChongdaFasong attempt = lockAttempt(form);
        DmsWenjianChongdaFasong saved = mapper.findDocument(attempt.getTenantId(), attempt.getId());
        if (saved != null) attempt.setDocumentId(saved.getDocumentId());
        return attempt;
    }
 
    /** 原生主子表保存完成后只确认执行副作用;不重新打印、取号或生成发放。 */
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianChongdaFasong finish(DmsWenjianChongdaForm form) throws DataException {
        DmsWenjianChongdaFasong attempt = lockAttempt(form);
        Map<String, Object> before = controlSnapshot(attempt);
        if ("CLOSED".equals(attempt.getState())) return attempt;
        if (!"CONFIRMED".equals(attempt.getState()) || attempt.getSubmittedAt() == null)
            throw new DataException("尚未收到全部重打成功回传,不能登记执行结果");
        DmsWenjianBupinghengExecution doc = requireNativeDocument(attempt);
        if (doc.getZhixingShijian() != null && !doc.getZhixingShijian().equals(attempt.getSubmittedAt()))
            throw new DataException("登记执行时间与成功回传不一致,请核对原记录");
        if (doc.getZhixingShijian() == null) {
            if (mapper.completeDocument(attempt) != 1)
                throw new DataException("重打已发送,登记确认失败,请只恢复登记");
            businessAudit.record(attempt.getTenantId(), "DMS_REPRINT_EXECUTION_CONFIRMED", "回写重打执行结果",
                    "dms_wenjian_bupingheng_dengji", attempt.getDocumentId(),
                    values("zhixing_shijian", doc.getZhixingShijian()),
                    values("zhixing_shijian", attempt.getSubmittedAt()),
                    attempt.getReason(), controlContext(attempt));
        }
        attempt.setState("CLOSED"); saveAttempt(attempt);
        recordControl("DMS_REPRINT_COMPLETE", "重新打印完成", attempt, before);
        return attempt;
    }
 
    /** 发送成功后校验原生单据并绑定,不接管主子表CRUD。 */
    private DmsWenjianBupinghengExecution requireNativeDocument(DmsWenjianChongdaFasong attempt) throws DataException {
        DmsWenjianChongdaFasong saved = mapper.findDocument(attempt.getTenantId(), attempt.getId());
        if (saved == null) throw new DataException("请先通过原生表单保存本次重打登记");
        attempt.setDocumentId(saved.getDocumentId());
        DmsWenjianBupinghengExecution doc = documents.lockDocument(attempt.getTenantId(), attempt.getDocumentId());
        List<DmsWenjianBupinghengExecution.Item> items = documents.lockItems(attempt.getTenantId(), attempt.getDocumentId());
        if (doc == null || !Objects.equals(doc.getBizSign(), attempt.getBizSign())
                || !Objects.equals(doc.getDengjiRenId(), attempt.getUserId()) || items == null || items.size() != 1)
            throw new DataException("原生登记与本次重打的签名、执行人或明细不匹配");
        DmsWenjianBupinghengExecution.Item item = items.get(0);
        if (!"cuoshi3".equals(item.getCuoshi()) || !Objects.equals(item.getDayinJiluId(), attempt.getPrintRecordId())
                || !Objects.equals(item.getYuanyin(), attempt.getReason()) || !blank(item.getXinDayinRenwuId()))
            throw new DataException("重打登记不得改变原记录、措施或原因");
        return doc;
    }
 
    private DmsWenjianChongdaFasong lockAttempt(DmsWenjianChongdaForm form) throws DataException {
        if (form == null || blank(form.getPrintRecordId())) throw new DataException("原打印记录不能为空");
        requireUuid(form.getRequestId());
        UserInfo actor = identity.requireUser(); String tenant = identity.tenantId(actor);
        // 与prepare一致先锁副本;不因发送后的状态变化丢弃真实成功回执。
        taskMapper.selectPrintForUpdate(tenant, form.getPrintRecordId(), null);
        DmsWenjianChongdaFasong attempt = mapper.selectAttempt(tenant, form.getRequestId());
        if (attempt == null || !Objects.equals(attempt.getPrintRecordId(), form.getPrintRecordId()))
            throw new DataException("重打发送任务不存在或关联不匹配");
        requireOwner(attempt, actor);
        return attempt;
    }
    private void requireOwner(DmsWenjianChongdaFasong attempt, UserInfo actor) throws DataException {
        if (!Objects.equals(attempt.getUserId(), actor.getUserId()))
            throw new DataException("该副本存在其他用户的未完成重打,请由原发起人恢复");
    }
    private void eligible(DmsWenjianDayinJiluEntity copy) throws DataException {
        if (copy == null || !("daifafang".equals(copy.getBizStatus()) || "daiqianshou".equals(copy.getBizStatus())))
            throw new DataException("仅待发放、待签收的有效打印记录允许重打");
    }
    private void requirePrintPermission(String tenant, String versionId) throws DataException {
        UUID id;
        try { id = UUID.fromString(versionId); } catch (RuntimeException e) { throw new DataException("文件版本ID无效"); }
        var version = fileVersions.selectReadableByTenantAndId(tenant, id);
        if (version == null) throw new DataException("原文件版本不存在或已删除");
        if (jnpf.dmsPermission.DmsFileControlPolicy.requiresPermissionCheck(version.getShifouShoukong()))
            permissions.requireAllowed(jnpf.dmsEntity.permission.enums.DmsResourceType.FILE_VERSION, id,
                    jnpf.dmsEntity.permission.enums.DmsPermissionAction.PRINT);
    }
    private void requireUuid(String value) throws DataException {
        try { if (!UUID.fromString(value).toString().equals(value)) throw new IllegalArgumentException(); }
        catch (RuntimeException ex) { throw new DataException("重打发送关联ID无效"); }
    }
    private boolean blank(String value) { return value == null || value.trim().isEmpty(); }
 
    /** 计划持久化后才能发;已有计划只能恢复,不能被另一窗口替换。 */
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianChongdaFasong plan(DmsWenjianChongdaForm form, String fileVersionId, int pageCount) throws DataException {
        DmsWenjianChongdaFasong attempt = lockAttempt(form);
        Map<String, Object> before = controlSnapshot(attempt);
        if (!batches(attempt).isEmpty() || !"OPEN".equals(attempt.getState())) return attempt;
        if (!Objects.equals(attempt.getFileVersionId(), fileVersionId) || pageCount < 1 || pageCount > 10000)
            throw new DataException("原版本或完整页数校验失败");
        validatePlan(form.getBatches(), attempt, pageCount);
        attempt.setPageCount(pageCount);
        attempt.setBatchesJson(JsonUtil.getObjectToString(form.getBatches()));
        saveAttempt(attempt);
        recordControl("DMS_REPRINT_PLAN", "冻结重打发送计划", attempt, before);
        return attempt;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianChongdaFasong split(DmsWenjianChongdaForm form) throws DataException {
        DmsWenjianChongdaFasong attempt = lockAttempt(form);
        Map<String, Object> before = controlSnapshot(attempt);
        List<Batch> list = batches(attempt);
        Batch batch = findBatch(list, form.getBatchId());
        if (!"OPEN".equals(attempt.getState()) || !"READY".equals(batch.getState())) throw new DataException("已发送的批次不能拆分");
        List<Batch> parts = form.getBatches();
        if (parts == null || parts.size() != 2) throw new DataException("拆分计划无效");
        validateBatch(parts.get(0)); validateBatch(parts.get(1));
        List<Integer> pages = new ArrayList<>(parts.get(0).getPages()); pages.addAll(parts.get(1).getPages());
        if (!parts.get(0).getItems().equals(batch.getItems()) || !parts.get(1).getItems().equals(batch.getItems())
                || !pages.equals(batch.getPages())) throw new DataException("拆分页段与原批次不一致");
        int index = list.indexOf(batch); list.remove(index); list.addAll(index, parts);
        if (list.stream().map(Batch::getId).distinct().count() != list.size()) throw new DataException("批次ID重复");
        attempt.setBatchesJson(JsonUtil.getObjectToString(list)); saveAttempt(attempt);
        recordControl("DMS_REPRINT_BATCH_SPLIT", "调整未派发重打页段", attempt, before);
        return attempt;
    }
 
    /** 非幂等派发许可:许可响应丢失也不能再次取得许可,必须保留结果未知。 */
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianChongdaFasong begin(DmsWenjianChongdaForm form) throws DataException {
        DmsWenjianChongdaFasong attempt = lockAttempt(form);
        Map<String, Object> before = controlSnapshot(attempt);
        List<Batch> list = batches(attempt);
        Batch batch = findBatch(list, form.getBatchId());
        if (!"OPEN".equals(attempt.getState()) || !"READY".equals(batch.getState())) {
            throw new DataException("该批次已发送或发送结果未知,请恢复原任务,禁止重复发送");
        }
        for (Batch previous : list) {
            if (previous == batch) break;
            if (!"SUBMITTED".equals(previous.getState())) throw new DataException("前一批次尚未确认,不能发送后续批次");
        }
        eligible(taskMapper.selectPrintForUpdate(attempt.getTenantId(), attempt.getPrintRecordId(), null));
        // 新流程先打印后登记;兼容旧流程已保存的单据,仍核对一致性。
        if (mapper.findDocument(attempt.getTenantId(), attempt.getId()) != null) {
            DmsWenjianBupinghengExecution doc = requireNativeDocument(attempt);
            if (doc.getZhixingShijian() != null) throw new DataException("本次登记已执行,不能再次发送重打");
        }
        requireUuid(form.getToken());
        batch.setToken(form.getToken()); batch.setState("SENDING");
        attempt.setBatchesJson(JsonUtil.getObjectToString(list)); saveAttempt(attempt);
        recordControl("DMS_REPRINT_BATCH_BEGIN", "取得重打派发许可", attempt, before);
        return attempt;
    }
 
    /** 包括迟到回传;保存失败可用同一令牌重试,绝不重新派发。 */
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianChongdaFasong submitted(DmsWenjianChongdaForm form) throws DataException {
        DmsWenjianChongdaFasong attempt = lockAttempt(form);
        Map<String, Object> before = controlSnapshot(attempt);
        List<Batch> list = batches(attempt);
        Batch batch = findBatch(list, form.getBatchId());
        requireUuid(form.getToken());
        if (!Objects.equals(form.getToken(), batch.getToken())
                || !("SENDING".equals(batch.getState()) || "SUBMITTED".equals(batch.getState()))) {
            throw new DataException("打印批次成功回传与发送任务不匹配");
        }
        if ("SUBMITTED".equals(batch.getState())) return attempt;
        batch.setState("SUBMITTED");
        attempt.setBatchesJson(JsonUtil.getObjectToString(list));
        if (list.stream().allMatch(item -> "SUBMITTED".equals(item.getState()))) {
            attempt.setState("CONFIRMED");
            attempt.setSubmittedAt(new Date());
        }
        saveAttempt(attempt);
        recordControl("DMS_REPRINT_BATCH_RECEIPT", "确认重打提交回传", attempt, before);
        return attempt;
    }
 
    private List<Batch> batches(DmsWenjianChongdaFasong attempt) {
        return JsonUtil.getJsonToList(attempt.getBatchesJson(), Batch.class);
    }
 
    private Batch findBatch(List<Batch> list, String id) throws DataException {
        for (Batch batch : list) if (Objects.equals(batch.getId(), id)) return batch;
        throw new DataException("打印批次不存在,请恢复原任务");
    }
 
    private void validateBatch(Batch batch) throws DataException {
        if (batch == null) throw new DataException("打印批次无效");
        requireUuid(batch.getId());
        if (batch.getPages() == null || batch.getPages().isEmpty() || batch.getPages().size() > 100
                || batch.getItems() == null || batch.getItems().size() != 1 || batch.getItems().get(0) == null || batch.getItems().get(0) < 0) throw new DataException("打印页段无效");
        batch.setState("READY"); batch.setToken(null);
    }
 
    private void validatePlan(List<Batch> list, DmsWenjianChongdaFasong attempt, int pageCount) throws DataException {
        if (list == null || list.isEmpty() || list.size() > 100000) throw new DataException("打印计划无效");
        Set<String> ids = new HashSet<>();
        int cursor = 0, expectedPages = 0;
        for (int copy = 0; copy < attempt.getCopies(); copy++) {
            int page = 1;
            while (cursor < list.size() && Collections.singletonList(copy).equals(list.get(cursor).getItems())) {
                Batch batch = list.get(cursor++);
                validateBatch(batch);
                if (!ids.add(batch.getId())) throw new DataException("批次ID重复");
                for (Integer number : batch.getPages()) {
                    if (!Objects.equals(number, page++) || page > 10001) throw new DataException("打印计划必须连续覆盖完整文档,不能重复或跳页");
                }
            }
            if (page == 1 || (copy > 0 && page - 1 != expectedPages)) throw new DataException("每份必须包含相同完整页段");
            expectedPages = page - 1;
        }
        if (cursor != list.size() || expectedPages != pageCount) throw new DataException("打印计划必须覆盖原版本全部页面");
    }
 
 
    private Map<String, Object> controlSnapshot(DmsWenjianChongdaFasong attempt) {
        List<Map<String, Object>> batchStates = batches(attempt).stream().map(batch ->
                values("batch_id", batch.getId(), "pages", batch.getPages(), "items", batch.getItems(),
                        "state", batch.getState())).toList();
        return values("fasong_zhuangtai", attempt.getState(), "pici_neirong", batchStates,
                "zong_yeshu", attempt.getPageCount(), "fasong_chenggong_shijian", attempt.getSubmittedAt(),
                "dengji_id", attempt.getDocumentId(), "quxiao_shijian", attempt.getCancelledAt());
    }
 
    private Map<String, Object> controlContext(DmsWenjianChongdaFasong attempt) {
        return values("request_id", attempt.getId(), "print_record_id", attempt.getPrintRecordId(),
                "task_id", attempt.getTaskId(), "file_version_id", attempt.getFileVersionId(),
                "copies", attempt.getCopies(), "numbers", JsonUtil.getJsonToList(attempt.getNumbersJson(), DmsWenjianDayinHaoma.class),
                "document_id", attempt.getDocumentId(), "execution_user_id", attempt.getUserId(), "biz_sign", attempt.getBizSign());
    }
 
    private void recordControl(String action, String label, DmsWenjianChongdaFasong attempt, Map<String, Object> before) {
        businessAudit.record(attempt.getTenantId(), action, label, "dms_wenjian_chongda_fasong", attempt.getId(),
                before, controlSnapshot(attempt), attempt.getReason(), controlContext(attempt));
    }
 
    private void saveAttempt(DmsWenjianChongdaFasong attempt) throws DataException {
        if (mapper.updateAttempt(attempt) != 1) throw new DataException("保存打印发送进度失败");
    }
}