app持续交付实战

news2024/9/25 21:28:40

在这里插入图片描述


app持续交付实战

  • 一、学习目标
  • 二、优势
  • 三、子任务拆分
  • 四、环境依赖
    • 1、安卓 SDK
    • 2、安卓设备(真机 or 模拟器)
    • 3、Appium 自动化测试
    • 4、JDK
    • 5、Python3环境
    • 6、allure-commandline工具
    • 7、allure插件
  • 五、实战任务:串行执行 Jenkins Pipeline 项目
    • 1、创建顺序执行 Jenkins Pipeline 项目
    • 2、顺序任务流程图
    • 3、添加Pipeline 代码
    • 4、将代码添加到 Jenkins 项目中
    • 5、顺序执行的APP持续交付Pipeline任务运行调试
    • 6、查看邮件
  • 六、实战任务2:并行执行 Jenkins Pipeline 项目
    • 1、并行任务流程图
    • 2、添加Pipeline 代码
    • 3、将代码添加到 Jenkins 项目与运行演示
    • 4、查看邮件
  • 七、总结

一、学习目标

从产品源代码制作出可运行APP,自动部署到安卓手机,运行自动测试程序检验正确性
包含自动任务
APP Build 自动打包
APP Build 自动发布
APP Build 自动部署
APP Build UI 自动测试
邮件通知任务结果
利用Jenkins Pipeline 技术实现APP持续交付的全自动化流水线过程

二、优势

重复性定时任务最适合让计算机来做
减少人力参与,提升工作效率
让Build不再是阻碍测试进度的因素
自动化重用率高,维护频率低

三、子任务拆分

获取源码
编译、打包
部署app
自动化测试
发布可测包
邮件通知

四、环境依赖

1、安卓 SDK

在这里插入图片描述
在这里插入图片描述

2、安卓设备(真机 or 模拟器)

3、Appium 自动化测试

保证在本地脚本可以跑通

4、JDK

C:\Users\Lenovo>java -version
java version "11.0.18" 2023-01-17 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.18+9-LTS-195)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.18+9-LTS-195, mixed mode)

5、Python3环境

C:\Users\Lenovo>python
Python 3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

6、allure-commandline工具

C:\Users\Lenovo>where allure
D:\allure-commandline-2.13.2\allure-2.13.2\bin\allure
D:\allure-commandline-2.13.2\allure-2.13.2\bin\allure.bat

7、allure插件

在这里插入图片描述

五、实战任务:串行执行 Jenkins Pipeline 项目

1、创建顺序执行 Jenkins Pipeline 项目

在这里插入图片描述

2、顺序任务流程图

在这里插入图片描述

3、添加Pipeline 代码

文件名:androidSamplePython_seq
在这里插入图片描述

pipeline{
    agent {
        label 'app_CICD_node'          //表示在那个节点运行
    }

    stages{
        stage('安卓程序源码同步') {
            steps {                                 // steps:代表执行步骤
                bat 'mkdir AndroidSampleApp'      // 创建一个AndroidSampleApp目录,如果目录存在不创建,目录不存在就创建
                dir("AndroidSampleApp"){            // 把git远程仓库代码,生成到AndroidSampleApp目录中
                    git branch:'master', url:'git@gitee.com:zhilongya/AndroidSampleApp.git'
                }
            }
        }



        stage('安卓编译打包') {
            steps {
                //进入子目录下AndroidSampleApp
                //运行bat gradlew clean assembleDebug:安卓编译打包
                // bat '''
                //     cd AndroidSampleApp             
                //     sh gradlew clean assembleDebug
                // '''
                echo "This is test code"
            }
        }


        stage('安卓部署') {
            steps {
                // 进入AndroidSampleApp目录下,
                // apk=app/build/outputs/apk/debug/app-debug.apk:变量的赋值
 
                bat '''
                    cd AndroidSampleApp
                    set apk="D:/jenkins_CICD/qywx_185637.apk"
                    echo %apk%
                    {  
                        adb uninstall com.tencent.wework
                    } || {
                        echo 'no com.tencent.wework package'
                    }
                    #TIMEOUT /T 5
                    echo %apk%
                    adb install -r %apk%
                '''
                
            }
        }

        stage('自动测试程序源码同步') {
            steps {
                bat 'mkdir iAppBVT_Python'
                dir("iAppBVT_Python"){
                    git branch:'master', url:'git@gitee.com:zhilongya/iAppBVT_Python.git'
                }
            }
        }

        stage('运行自动化测试') {
            steps {
                bat '''
                    # . ~/.bash_profile

                    cd iAppBVT_Python

                    #更新python依赖库
                    pip3.9 install -r requirements.txt

                    #运行自动化测试
                    #pytest -sv test/bvt_test.py --tc-file ~/iAppBVT_Python.json --tc-format json
                    pytest -sv test/test_bvt.py
                '''
            }
        }
    }

    post {
        success {
            // archiveArtifacts artifacts: 'D:/jenkins_CICD/qywx_185637'
            echo "This is test code"
        }

        always{
            script{
                emailext body: 
                '''  <!DOCTYPE html>
                <html>
                <head>
                <meta charset="UTF-8">
                <title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
                </head>
                <body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0">
                    <table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
                        <tr>
                            <td>(本邮件由程序自动下发,请勿回复!)</td>
                        </tr>
                        <tr>
                            <td>
                                <h2><font color="#FF0000">构建结果 - ${BUILD_STATUS}</font></h2>
                            </td>
                        </tr>
                        <tr>
                            <td><br />
                                <b><font color="#0B610B">构建信息</font></b>
                                <hr size="2" width="100%" align="center" />
                            </td>
                        </tr>
                        <tr><a href="${PROJECT_URL}">${PROJECT_URL}</a>
                            <td>
                                <ul>
                                    <li>项目名称:${PROJECT_NAME}</li>
                                    <li>GIT路径:<a href="git@gitlab.corp.XXX.com:wangxu/ops.git">git@gitlab.corp.XXX.com:wangxu/ops.git</a></li>   
                                    <li>GIT分支:master</li>
                                    <li>构建编号:${BUILD_NUMBER}</li>                    
                                    <li>触发原因:${CAUSE}</li>   
                                    <li>构建日志:<a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
                                </ul>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <b><font color="#0B610B">变更信息:</font></b>
                                <hr size="2" width="100%" align="center" />
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <ul>
                                    <li>上次构建成功后变化 :  ${CHANGES_SINCE_LAST_SUCCESS}</a></li>
                                </ul>    
                            </td>
                        </tr>
                    <tr>
                            <td>
                                <ul>
                                    <li>上次构建不稳定后变化 :  ${CHANGES_SINCE_LAST_UNSTABLE}</a></li>
                                </ul>    
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <ul>
                                    <li>历史变更记录 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li>
                                </ul>    
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <ul>
                                    <li>变更集:${JELLY_SCRIPT,template="html"}</a></li>
                                </ul>    
                            </td>
                        </tr>
                        <!--
                        <tr>
                            <td>
                                <b><font color="#0B610B">Failed Test Results</font></b>
                                <hr size="2" width="100%" align="center" />
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <pre style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">$FAILED_TESTS</pre>
                                <br />
                            </td>
                        </tr>
                        
                        <tr>
                            <td>
                                <b><font color="#0B610B">构建日志 (最后 100行):</font></b>
                                <hr size="2" width="100%" align="center" />
                            </td>
                        </tr>-->
                        <!-- <tr>
                            <td>Test Logs (if test has ran): <a
                                href="${PROJECT_URL}ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip">${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip</a>
                                <br />
                            <br />
                            </td>
                        </tr> -->
                        <!--
                        <tr>
                            <td>
                                <textarea cols="80" rows="30" readonly="readonly" style="font-family: Courier New">${BUILD_LOG, maxLines=100,escapeHtml=true}</textarea>
                            </td>
                        </tr>-->
                        <hr size="2" width="100%" align="center" />
                    
                    </table>
                    
                    
                </body>
                </html>
''', 
              subject: '$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!', to: '1181068365@qq.com'
          }
          }

          }

}

4、将代码添加到 Jenkins 项目中

在这里插入图片描述
在这里插入图片描述

5、顺序执行的APP持续交付Pipeline任务运行调试

在这里插入图片描述

6、查看邮件

在这里插入图片描述

六、实战任务2:并行执行 Jenkins Pipeline 项目

流程效率的优化
目标:节约运行时间,提升效率
方法:抽取可并行的节点,减少等待时间

1、并行任务流程图

在这里插入图片描述

2、添加Pipeline 代码

文件名:androidSamplePython_paral
在这里插入图片描述



pipeline{
    agent {
        label 'app_CICD_node'
    }

    stages{
        stage('获取源码') {
            parallel {
                stage('安卓程序源码') {
                    steps {
                        bat 'mkdir AndroidSampleApp'
                        dir("AndroidSampleApp"){
                            git branch:'master', url:'git@gitee.com:zhilongya/AndroidSampleApp.git'
                        }
                    }
                }

                stage('自动测试程序源码') {
                    steps {
                        bat 'mkdir iAppBVT_Python'
                        dir("iAppBVT_Python"){
                            git branch:'master', url:'git@gitee.com:zhilongya/iAppBVT_Python.git'
                        }
                    }
                }
            }
        }

        stage('安卓编译打包') {
            steps {
                bat '''
                    cd AndroidSampleApp
                    gradlew clean assembleDebug
                '''
                //echo "This is test code"
            }
        }

        stage('测试与发布') {
            parallel {
                stage('发布测试包') {
                    steps {
                        archiveArtifacts artifacts: 'AndroidSampleApp/app/build/outputs/apk/debug/app-debug.apk'
                        //echo "This is test code"
                    }
                }

                stage('自动化'){
                    stages{
                        stage('部署') {
                            steps {
                                bat '''
                                    cd AndroidSampleApp
                                    // set apk=app/build/outputs/apk/debug/app-debug.apk
                                    set apk="D:/jenkins_CICD/qywx_185637.apk"
                                    {
                                        #try: 卸载现有的安卓app
                                        adb uninstall com.tencent.wework
                                    } || {
                                        #catch
                                        echo 'no com.tencent.wework package'
                                    }
                                    #sleep 5

                                    #安装安卓app
                                    echo %apk%
                                    adb install -r %apk%
                                '''
                            }
                        }

                        stage('自动测试') {
                            steps {
                                bat '''
                                    cd iAppBVT_Python

                                    #更新python依赖库
                                    pip3.9 install -r requirements.txt

                                    #运行自动化测试
                                    #pytest -sv test/bvt_test.py --tc-file ~/iAppBVT_Python.json --tc-format json
                                    pytest -sv test/test_bvt.py
                                    '''
                            }
                        }
                    }
                }
            }
        }
    }

    post {
        success {
            // archiveArtifacts artifacts: 'D:/jenkins_CICD/qywx_185637'
            echo "This is test code"
        }

        always{
            script{
                emailext body: 
                '''  <!DOCTYPE html>
                <html>
                <head>
                <meta charset="UTF-8">
                <title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
                </head>
                <body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0">
                    <table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
                        <tr>
                            <td>(本邮件由程序自动下发,请勿回复!)</td>
                        </tr>
                        <tr>
                            <td>
                                <h2><font color="#FF0000">构建结果 - ${BUILD_STATUS}</font></h2>
                            </td>
                        </tr>
                        <tr>
                            <td><br />
                                <b><font color="#0B610B">构建信息</font></b>
                                <hr size="2" width="100%" align="center" />
                            </td>
                        </tr>
                        <tr><a href="${PROJECT_URL}">${PROJECT_URL}</a>
                            <td>
                                <ul>
                                    <li>项目名称:${PROJECT_NAME}</li>
                                    <li>GIT路径:<a href="git@gitlab.corp.XXX.com:wangxu/ops.git">git@gitlab.corp.XXX.com:wangxu/ops.git</a></li>   
                                    <li>GIT分支:master</li>
                                    <li>构建编号:${BUILD_NUMBER}</li>                    
                                    <li>触发原因:${CAUSE}</li>   
                                    <li>构建日志:<a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
                                </ul>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <b><font color="#0B610B">变更信息:</font></b>
                                <hr size="2" width="100%" align="center" />
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <ul>
                                    <li>上次构建成功后变化 :  ${CHANGES_SINCE_LAST_SUCCESS}</a></li>
                                </ul>    
                            </td>
                        </tr>
                    <tr>
                            <td>
                                <ul>
                                    <li>上次构建不稳定后变化 :  ${CHANGES_SINCE_LAST_UNSTABLE}</a></li>
                                </ul>    
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <ul>
                                    <li>历史变更记录 : <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li>
                                </ul>    
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <ul>
                                    <li>变更集:${JELLY_SCRIPT,template="html"}</a></li>
                                </ul>    
                            </td>
                        </tr>
                        <!--
                        <tr>
                            <td>
                                <b><font color="#0B610B">Failed Test Results</font></b>
                                <hr size="2" width="100%" align="center" />
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <pre style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">$FAILED_TESTS</pre>
                                <br />
                            </td>
                        </tr>
                        
                        <tr>
                            <td>
                                <b><font color="#0B610B">构建日志 (最后 100行):</font></b>
                                <hr size="2" width="100%" align="center" />
                            </td>
                        </tr>-->
                        <!-- <tr>
                            <td>Test Logs (if test has ran): <a
                                href="${PROJECT_URL}ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip">${PROJECT_URL}/ws/TestResult/archive_logs/Log-Build-${BUILD_NUMBER}.zip</a>
                                <br />
                            <br />
                            </td>
                        </tr> -->
                        <!--
                        <tr>
                            <td>
                                <textarea cols="80" rows="30" readonly="readonly" style="font-family: Courier New">${BUILD_LOG, maxLines=100,escapeHtml=true}</textarea>
                            </td>
                        </tr>-->
                        <hr size="2" width="100%" align="center" />
                    
                    </table>
                    
                    
                </body>
                </html>
''', 
              subject: '$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!', to: '1181068365@qq.com'
          }
          }

          }
}

3、将代码添加到 Jenkins 项目与运行演示

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4、查看邮件

在这里插入图片描述

七、总结

部分并行相比于顺序执行效率有了明显的提升
利用 Jenkins Pipeline 代码控制APP持续交付的全流程
持续交付的子任务拆分: 减少工作节点之间的耦合性,提高节点本身操作的内聚性
效率提升:并行与串行控制
利用代码控制交付过程,灵活、方便、有效

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/495304.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python学习之批量转换图片格式和统一图片尺寸

前言 大家在工作的时候是不是都会接触到很多的图片&#xff0c;为了满足不同的需求&#xff1a; 兼容性&#xff1a;不同设备和应用程序可能支持不同的图片格式。通过转换图片格式&#xff0c;可以确保在各种设备和应用程序中都能够正确地显示图片。 文件大小&#xff1a;不…

基于matlab 从接收脉冲中提取波形参数

一、前言 现代飞机通常随身携带雷达警告接收器 &#xff08;RWR&#xff09;。RWR检测到雷达发射&#xff0c;并在雷达信号照射到飞机上时警告飞行员。RWR不仅可以检测雷达发射&#xff0c;还可以分析截获的信号并编目信号来自哪种雷达。此示例显示了 RWR 如何估计截获脉冲的参…

10倍速度开发贪吃蛇游戏之AI辅助

今天就来聊聊AI代码辅助神器&#xff0c;即便是零基础也能上手&#xff0c;因为实在是太强了&#xff0c;这以后叫程序员们怎么活啊&#xff01;话不多说&#xff0c;直接上神器 我用的是cursor,其实目前AI辅助代码不止cursor&#xff0c;还有微软家的copilot X这个根植于gith…

JVM-01-JVM知识

1-JVM内存模型 Java开发人员一般情况下&#xff0c;使用过程中&#xff0c;不用关注内存的申请和释放&#xff0c;得益于JVM自动内存分配机制&#xff0c;但是其实是个双刃剑&#xff0c;这可以提升Java开发的效率&#xff0c;但是弱化了开发人员内存管理意识&#xff0c;系统容…

四元数快速入门【Quaternion】

四元数&#xff08;Quaternion&#xff09;是用于旋转和拉伸向量的数学运算符。 本文提供了一个概述&#xff0c;以帮助理解在空间导航等应用程序中对四元数的需求。 推荐&#xff1a;用 NSDT场景设计器 快速搭建3D场景。 可以通过多种方式在空间中准确定位、移动和旋转物体。 …

U盘在电脑上读不出来怎么办?详细解决方法在这!

案例&#xff1a;u盘在电脑上读不出来 【不知道为什么&#xff0c;我把u盘插入电脑后电脑完全读不出。我也不知道到底哪里出现了问题&#xff0c;有人可以帮我解答一下吗&#xff1f;】 U盘作为我们生活中经常使用的存储工具&#xff0c;其能帮我们存储大量的文件&#xff0c…

BM54-三数之和

题目 给出一个有n个元素的数组S&#xff0c;S中是否有元素a,b,c满足abc0&#xff1f;找出数组S中所有满足条件的三元组。 数据范围&#xff1a;0≤n≤1000&#xff0c;数组中各个元素值满足 ∣val∣≤100。 空间复杂度&#xff1a;O(n^2)&#xff0c;时间复杂度 O(n^2)。 注…

React 第三方插件 —— Cron 表达式生成器(qnn-react-cron)

qnn-react-cron 可以看做 react-cron-antd 的升级版&#xff08;具体“渊源”可见文档&#xff09;&#xff0c;现有功能如下&#xff1a; &#x1f389; 全面支持 cron&#xff1a;秒、分、时、日、月、周、年 &#x1f389; 日及周条件互斥&#xff0c;自动改变响应值 &…

8年测试开发,写给1-3年功能测试的几点建议,满满硬货指导

从15年毕业到现在也从业八年了&#xff0c;普通本科毕业&#xff0c;现在一家互联网公司担任测试部门总监&#xff0c;摸爬打滚&#xff0c;坑坑洼洼也经历了不少。思绪很久决定还是写下这篇&#xff0c;希望对后进的小伙子少走一点弯路。 很多人把职场想得太美好&#xff0c;其…

学node写接口!!!

fs 可以读取文档 fs.readFild() 用于读取文件 第一个参数 路径 第二个参数 "utf8"(默认值) 第三个参数 函数 function(err , dataStr ){ 第一个参数是错误&#xff0c; 第二个参数是正确的可以拿到读取文件里面的值 } fs.writeFile() 用于创建文件添加内容 …

【LeetCode中等】1419.数青蛙

给你一个字符串 croakOfFrogs&#xff0c;它表示不同青蛙发出的蛙鸣声&#xff08;字符串 “croak” &#xff09;的组合。由于同一时间可以有多只青蛙呱呱作响&#xff0c;所以 croakOfFrogs 中会混合多个 “croak” 。 请你返回模拟字符串中所有蛙鸣所需不同青蛙的最少数目。…

如何根据参考文献查找原文及详细的文献信息

当我们已知参考文献想要查看下载原文以及相关信息时可以用下面的方法&#xff1a; 例如这篇参考文献&#xff1a;Alsamhi S H, Almalki F, Ma O, et al. Predictive estimation of optimal signal strength from drones over IoT frameworks in smart cities[J]. IEEE Transac…

FPGA实现SDI视频解码PCIE传输 提供工程源码和QT上位机源码加技术支持

目录 1、前言2、我已有的SDI编解码方案3、我已有的PCIE方案4、总体设计思路和方案SDI摄像头Gv8601a单端转差GTX解串SDI解码VGA时序恢复YUV转RGB图像缓存PCIE发送通路SDI同步输出通路 5、vivado工程详解6、驱动安装7、QT上位机软件8、上板调试验证SDI同步HDMI输出验证PCIE输出验…

neo4j结合gds实现最短路径算法

背景&#xff1a;Neo4j自带的cypher语句中的 shortestpath allShortestPaths 返回值内容非常有限&#xff0c;不易处理, 在实际生产环境中可用性极低&#xff0c; 且若带where条件查询时&#xff0c;查询效率极低 因此&#xff0c;使用Neo4j自带的插件如apoc来进行最短路径查询…

使用@Autowired、@Qualifier、@Primary注解自动装配组件

1.Autowired、Qualifier、Primary注解 1.1.Autowired注解 Autowired注解可以对类成员变量、方法和构造函数进行标注&#xff0c;完成自动装配的工作。 package org.springframework.beans.factory.annotation;import java.lang.annotation.Documented; import java.lang.ann…

hexo stellar主题添加运行时间-利用不蒜子

在网站的页脚想添加博客运行的时间以及&#xff0c;查了一下资料&#xff0c;根据下面的这个博主的代码进行了修改。 打造一个舒服的写作环境&#xff08;Hexo&#xff09; 下面我们开始修改&#xff1a; stellar/layout/_partial/main/footer.ejs 找到//footer的 这一段 // …

基于logback 实现springboot的日志配置

目录 一、前言 二、使用详解 2.1、打印到文件中 2.2、打印级别控制 2.3、logback 详细配置 2.4、logback 配置文件的组成 2.4.1、<root>标签 2.4.2、<contextName>标签 2.4.3、<property>标签 2.4.4、<appender>标签 2.4.5、<logger&g…

Mysql索引(2):索引结构

1 概述 MySQL的索引是在存储引擎层实现的&#xff0c;不同的存储引擎有不同的索引结构&#xff0c;主要包含以下几种&#xff1a; 索引结构描述BTree索最常见的索引类型&#xff0c;大部分引擎都支持 B 树索引 Hash索引 底层数据结构是用哈希表实现的, 只有精确匹配索引列的…

ShardingProxy分库分表实战

目录 ShardingProxy简述 快速使用 ShardingProxy部署 ShardingProxy使用 ShardingProxy的服务治理 Shardingproxy的其他功能 ShardingProxy的SPI扩展 ShardingSphere总结 ShardingProxy简述 ShardingProxy的功能同样是分库分表&#xff0c;但是他是一个独立部署的服务…

林曦讲座 | 关于“无用之美”的11个闪光

林曦老师的新书《无用之美》即将和大家见面啦~    究竟什么是“无用之美”呢&#xff1f;早在2013年&#xff0c;林曦老师就曾在“一席”做过一场关于“无用之美”的分享。2019年又在微软做过一次《中国人的闲情逸致——无用之美》的演讲。这次将两次演讲的内容精华整理出来…