腾博会官网

【数据猿|金猿案例展】国元证券——建立一体化智能可观测平台实现APP体验数字化

2024-01-18

近年来 ,证券期货业移动应用体系建设快速发展 ,环境日臻完善 ,工具应用日益广泛 ,移动应用提供了快速便捷的证券业务服务 。证券类APP活跃用户规模持续上升 ,智能移动炒股成为行业标配 。据艾媒咨询数据显示 ,2021年中国证券APP用户规模为1.5亿人 。随着中国证券市场的不断成熟以及投资者数量的持续增多 ,中国证券APP用户规模仍将保持较快增长 ,预计2025年达2.6亿人 。

国元证券金融科技部从2021年开始建立体系化应用性能管理平台 ,从高频功能操作自动化执行 、APP崩溃卡顿 、兼容性bug 、启动速度等多个角度把控APP质量 ,提升APP功能应用和性能体验 。

随着证券期货移动端APP用户规模持续上升 ,移动端APP的网络环境变得越来越复杂 ,设备型号众多 ,使用场景多元 。面对广大的用户需求及高时效性的证券业务特点 ,如何通过保障系统质量 ,提升用户服务 ,有机结合国家金融安全与行业数字化发展 ,探索安全可靠的数字化技术 、新模式 ,成为APP产品质量保障的下一步重要工作 。

实施时间 :

项目开始时间 :2021年6月

中间重要时间节点 :2023年6月

项目完结时间 :本项目由国元证券金融科技部主导 ,项目实施周期2年 。其中 ,金融科技部两名测试开发人员 、一名系统架构师参与项目开发与实施 。

应用场景

本方案立足国家标准和公司移动端应用的实际情况 ,着眼于发展和完善测试质量体系 ,通过技术创新和模式创新相结合 ,以创造更高更优质的服务体验为目标 。

针对证券用户特有的高频使用场景 ,制定了以下场景策略 。

稳定APP版本 :针对不同机型可能存在的兼容性问题 ,国元证券通过收集和分析版本的性能数据 ,及时发现并解决潜在的问题 。通过模拟用户常用场景 ,国元证券能够提供更稳定 、更可靠的线上版本 ,确保用户在使用过程中获得更好的体验 。

新交易上线保障 :在新的交易功能上线过程中 ,国元证券通过模拟各种业务场景的崩溃和卡顿情况 ,为灰度逐步发布提供保障 。这有助于确保新功能的顺利推出 ,减少故障历时和运维成本 。同时 ,国元证券基于相关数据快速定位和解决问题 ,保障APP投产的稳定性和用户体验 。

客户端系统和硬件分布分析 :国元证券通过分析客户端系统和硬件的分布情况 ,提前安排架构适配工作 。这有助于提高APP的稳定性和用户体验 ,确保用户在不同设备和操作系统上都能够获得一致 、优质的服务体验 。

此外 ,国元证券还通过定期质检和持续数据分析 ,实现APP的“全方位”保障 。这有助于及时发现并解决潜在的问题 ,帮助用户快速定位故障点 ,减少故障历时和运维成本 。同时 ,根据长期的功能和非功能性测试的数据积累 ,国元证券建立了质量门禁 ,形成完善可持续的质量管理制度和测试指引 。

综上所述 ,本方案旨在为证券期货移动端APP的用户提供更高更优质的服务体验 。

面临挑战

证券期货业是一个瞬息万变的领域 ,具有场景多元化 、机型众多 ,及网络环境复杂等特点 ,为应对这些挑战 ,移动端APP的成功开发和运营需要综合运用先进的技术手段 、严谨的管理体系和敏锐的市场洞察 ,以应对行业的复杂性和多变性 。

从场景多元化的角度来看 ,证券期货市场的投资品种多样 ,投资策略各异 ,使得移动端APP的功能需求变得异常复杂 。不同的用户有不同的投资需求和习惯 ,这就要求APP的功能要全面 、灵活 ,能够满足不同用户的需求 。

机型众多也是证券期货移动端APP面临的一个挑战 。为满足用户的多样化需求 ,证券公司需要开发针对不同手机操作系统 、不同屏幕尺寸以及不同网络环境的移动端APP 。这不仅增加了开发成本和时间 ,也给后期测试和维护带来了很大的困难 。

由于用户所处的网络状态可能会不断变动 ,网络环境复杂是证券期货移动端APP所必须应对的重大挑战之一 。在交易过程中 ,用户可能会遇到网络延迟 、断线等问题 ,这会对用户的交易决策产生重大影响 。因此 ,移动端APP需要具备强大的网络容错能力和快速恢复能力 ,以确保用户能够顺利进行交易 。

已有的性能管理平台提供的性能指标能够主动的解决很多以崩溃卡顿为主的问题 。但是如何事前识别问题及事后高效精准快速的定位用户反馈过来的问题 ,成为APP产品质量保障的下一步重要工作 。

应用技术与实施过程

由腾博会官网提供的Bonree ONE 一体化智能可观测平台的实践应用中 ,移动端APP系统架构主要由手机端 、PC端 、Server端 、web端和大数据组成 ,通过终端采集性能数据 ,由PC端将数据传递到Server端进行数据回收与数据处理 ,处理后的数据存到数据库和到WEB端进行展示 。在合作初期 ,国元证券整合了Bonree ONE的可观测性技术 ,并搭建本地服务器进行性能管理 。

技术方案

性能管理平台通过无侵入方式采集请求响应时间 、DNS时间 、TCP时间 、SSL握手时间 、请求时间 、服务响应时间 、数据接收时间 、信号量 、请求错误发生时间等网络性能数据;同时可采集使用过程中视图性能 、崩溃 、卡顿报错日志 。

在APP上线发布阶段 ,随着业务拓展与调整 ,证券移动APP版本迭代和功能更新较快 ,新功能的增加是否仍然能保证APP具有高可用性和高性能 。结合客户端性能管理平台数据 ,对新版本性能数据重点保障 ,并对比历史版本 ,快速识别上线新问题并协助定位 。无异常时再逐步灰度发布 ,保障上线过程稳定性 。

主要目标如下 :

1 、用户体验 :了解用户性能体验对业务过程的影响情况 ,助力优化产品性能 ,提升用户体验和业务价值;

2 、问题分析:了解问题形成原因 ,掌握故障影响范围;

3 、事件追溯:针对特定问题提供回溯能力 ,确认问题原因 ,为事件处置提供日志支持 。

image-1705545851704

图 :性能平台架构

崩溃信息

APP崩溃是导致用户流失的重要因素之一 。由于大多数公司在APP上线之前无法做到在各种环境下的全面适配测试 ,出现崩溃在所难免 ,所以快速定位问题点及问题复现是崩溃分析的意义所在 。

image-1705545876587

image-1705545907157

崩溃分析报告可以让直观的了解所选时间范围内的崩溃数量 、崩溃率等概要信息 ,也可以通过视图 、OS版本 、设备型号 、APP版本等维度查看崩溃的分布 。

通过深入崩溃影响分析 ,支持对应用发生崩溃时的环境信息进行统计 。区分崩溃影响的独立用户数 ,统计某类崩溃在各类设备 、操作系统和各 APP 版本中出现的次数 ,被该崩溃影响的用户数 。

ANR分析

主要针对Android版APP独有的分析模块 。通过采集ANR堆栈信息 、AnrTrace 、ANR部件 、ANR类型等多维度数据 ,进行深入的ANR分析 ,准确发现线程阻塞 、挂起或死循环等问题 。通过还原ANR问题背景 ,可以帮助发现APP运行过程中 ,由于种种原因导致主线程阻塞 、挂起或死循环等问题 ,并帮助开发者分析定位产生的原因 ,为进一步优化APP稳定性和用户体验提供依据 。

image-1705545951963

卡顿率标准

卡顿分析主要展示当前查询时间范围内的卡顿次数 ,启动次数卡顿率 ,影响用户数等信息 ,在卡顿分析中通过流畅度定义卡顿 。

Android系统 :通过定义流畅度帧数来判断是否卡顿 。

IOS系统 :通过子线程监测检查一次循环时间是否超过一定值 ,判断是否卡顿 。

通过流畅度帧数来获取 ,依据流畅度定义(帧率为60fps)即尽量保证每次在16ms内处理完所有的CPU与GPU计算 、绘制 、渲染等操作 ,当每次处理过程超过16ms时 ,视为一次卡顿 。例如安卓卡顿标准 :主线程Runloop循环耗时超过5s判定为卡顿 。或者主线程vSync每秒钟循环少于40次 ,连续出现5个周期时也判定为卡顿 。

日志分析

image-1705545963796

日志诊断中的问题检索功能可以快速协助定位问题 ,并依据详细的日志信息和上下文 ,帮助解决问题 。

● 通过日志诊断功能在问题定位和解决方面具有显著优势 ,有助于提高用户体验和可靠性 。

● 通过日志分析 ,可以追踪应用程序的活动 、了解系统资源的使用情况 、监测网络连接等 。此外 ,日志分析还可以用于性能分析 、合规性审计和故障恢复等方面 。

商业变化

管理证券APP整体性能 ,精准定位疑难问题

通过可观测性性能管理系统宏观掌控APP质量 ,判断问题归属 。可以主动的关注定位协助解决例如崩溃卡顿等性能问题 ,提升用户体验 。同时针对用户反馈的应用问题 ,由于移动端APP复杂的网络环境 、设备型号众多 、使用场景多元 ,通常较难复现定位 ,通过平台日志进行分析 ,问题日志上下文 ,可以使问题定位所需时间从天降到分钟 。大幅度提升客户端APP的稳定性 。

建立质量门禁 ,避免APP迭代对证券用户常用场景带来的性能降低

积累功能测试和非功能性测试的数据 ,形成交互分析 、崩溃 、卡顿 、启动性能和资源消耗等性能指标 ,建立性能门禁 ,设置预警值 ,量化APP质量 ,形成可持续发展的质量保障制度体系 。

技术创新驱动 ,证券移动APP性能与质量全面提升

将证券移动APP性能多角度数据可视化 ,宏观管理APP质量 ,及时主动发现APP质量问题并主动修复 。同时将证券用户遇到的问题转换成IT语言 ,可快速还原IT语言事故现场 。节省运维和研发人员定位解决问题时间 。

经过对崩溃问题 、ANR问题 、卡顿问题的持续优化 ,结合腾博会官网Bonree ONE的迭代修复 ,APP稳定性达到优秀水平 。具体数据如下 :

1.崩溃率下降了90%;

2.ANR率下降了80%;

3.卡顿率下降了72% 。

这些改进不仅提高了用户体验 ,也进一步巩固了APP产品的稳定性和可靠性 。

相关企业介绍

·腾博会官网

北京博睿宏远数据科技股份有限公司(简称腾博会官网)(股票号688229)是中国IT运维监控和可观测性领域领导者 ,中国应用性能监控及可观测性领域唯一上市公司 ,同时蝉联市场份额排名第一 。专注于构建以用户为中心的简捷 ,高效 ,智能的新型IT运维 ,有效提升云资源利用效率 ,驱动业务创新增长 ,助力企业提升核心竞争力 ,抢占数字经济先机 。

15年以来 ,腾博会官网以深厚的技术积累不断打磨产品和服务能力 ,已在IT运维领域形成了自身的独特优势 ,并将一体化运维监控和AIOps等解决方案落地到各种客户生产环境之中 ,为银行 ,证券 ,保险 ,高端制造等行业的数字化 、智能化转型持续赋能 ,已经获得中国银行 、工商银行 、中国建设银行 、农业银行 、光大银行 、华夏银行 、平安银行 、招商银行 、中信银行等多家银行及1000+头部客户的选择和信赖 。

·国元证券

国元证券股份有限公司(股票号000728)是由原安徽省国际信托投资公司和原安徽省信托投资公司作为主发起人 ,于2001年10月成立 。经过20多年发展 ,国元证券成长成为业务牌照齐全 、业务品种多元 、业务发展遍布全国的综合金融服务商 。目前 ,公司拥有40家分公司 、106家证券营业部 ,业务范围通盖证券 、基金 、期货 、资产管理股权投资 、另类投资 、境外业务 、区域股权市场等专业领域 ,综合实力和核心业务位居行业前列 。

文章标签

一体化智能可观测

相关文章

FreeMarker template error (DEBUG mode; use RETHROW in production!): The string doesn't match the expected date/time/date-time format. The string to parse was: "2024年3月5日". The expected format was: "MMM d, y". The nested reason given follows: Unparseable date: "2024年3月5日" ---- FTL stack trace ("~" means nesting-related): - Failed at: #if "2024年3月5日"?date lt item.createTi... [in template "themes/halo_quickstarter/post_newsDetail.ftl" at line 106, column 25] ---- Java stack trace (for programmers): ---- freemarker.core._TemplateModelException: [... Exception message was already printed; see it above ...] at freemarker.core.BuiltInsForMultipleTypes$dateBI$DateParser.parse(BuiltInsForMultipleTypes.java:220) at freemarker.core.BuiltInsForMultipleTypes$dateBI$DateParser.getAsDateModel(BuiltInsForMultipleTypes.java:190) at freemarker.core.BuiltInsForMultipleTypes$dateBI$DateParser.getAsDate(BuiltInsForMultipleTypes.java:197) at freemarker.core.EvalUtil.modelToDate(EvalUtil.java:86) at freemarker.core.EvalUtil.compare(EvalUtil.java:270) at freemarker.core.EvalUtil.compare(EvalUtil.java:115) at freemarker.core.ComparisonExpression.evalToBoolean(ComparisonExpression.java:78) at freemarker.core.IfBlock.accept(IfBlock.java:49) at freemarker.core.Environment.visit(Environment.java:370) at freemarker.core.IteratorBlock$IterationContext.executedNestedContentForCollOrSeqListing(IteratorBlock.java:321) at freemarker.core.IteratorBlock$IterationContext.executeNestedContent(IteratorBlock.java:271) at freemarker.core.IteratorBlock$IterationContext.accept(IteratorBlock.java:244) at freemarker.core.Environment.visitIteratorBlock(Environment.java:644) at freemarker.core.IteratorBlock.acceptWithResult(IteratorBlock.java:108) at freemarker.core.IteratorBlock.accept(IteratorBlock.java:94) at freemarker.core.Environment.visit(Environment.java:334) at freemarker.core.Environment.visit(Environment.java:340) at freemarker.core.Environment.visit(Environment.java:340) at freemarker.core.Environment.process(Environment.java:313) at freemarker.template.Template.process(Template.java:383) at org.springframework.web.servlet.view.freemarker.FreeMarkerView.processTemplate(FreeMarkerView.java:391) at org.springframework.web.servlet.view.freemarker.FreeMarkerView.doRender(FreeMarkerView.java:304) at org.springframework.web.servlet.view.freemarker.FreeMarkerView.renderMergedTemplateModel(FreeMarkerView.java:255) at org.springframework.web.servlet.view.AbstractTemplateView.renderMergedOutputModel(AbstractTemplateView.java:179) at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:316) at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1401) at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1145) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1084) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) at javax.servlet.http.httpervlet.service(httpervlet.java:497) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) at javax.servlet.http.httpervlet.service(httpervlet.java:584) at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:799) at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1631) at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:230) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at run.halo.app.cas.UserInfoFilter.doFilter(UserInfoFilter.java:37) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at org.jasig.cas.client.util.AssertionThreadLocalFilter.doFilter(AssertionThreadLocalFilter.java:54) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at org.jasig.cas.client.util.httpervletRequestWrapperFilter.doFilter(httpervletRequestWrapperFilter.java:75) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at run.halo.app.cas.MyAbstractTicketValidationFilter.doFilter(MyAbstractTicketValidationFilter.java:215) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at run.halo.app.cas.MyAuthenticationNoLoginFilter.doFilter(MyAuthenticationNoLoginFilter.java:57) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at run.halo.app.security.filter.ContentFilter.doAuthenticate(ContentFilter.java:69) at run.halo.app.security.filter.AbstractAuthenticationFilter.doFilterInternal(AbstractAuthenticationFilter.java:229) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at run.halo.app.filter.CorsFilter.doFilter(CorsFilter.java:53) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at run.halo.app.filter.LogFilter.doFilterInternal(LogFilter.java:40) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:548) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143) at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:600) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235) at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1624) at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233) at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1440) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188) at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:501) at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1594) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186) at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1355) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) at org.eclipse.jetty.server.Server.handle(Server.java:516) at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:487) at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:732) at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:479) at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277) at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311) at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105) at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131) at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:409) at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883) at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034) at java.base/java.lang.Thread.run(Thread.java:834) Caused by: freemarker.core.UnparsableValueException: Unparseable date: "2024年3月5日" at freemarker.core.JavaTemplateDateFormat.parse(JavaTemplateDateFormat.java:51) at freemarker.core.JavaTemplateDateFormat.parse(JavaTemplateDateFormat.java:33) at freemarker.core.BuiltInsForMultipleTypes$dateBI$DateParser.parse(BuiltInsForMultipleTypes.java:213) ... 118 more Caused by: java.text.ParseException: Unparseable date: "2024年3月5日" at java.base/java.text.DateFormat.parse(DateFormat.java:395) at freemarker.core.JavaTemplateDateFormat.parse(JavaTemplateDateFormat.java:49) ... 120 more