<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Jason Tsang</title>
  
  
  <link href="https://jasontsang.dev/atom.xml" rel="self"/>
  
  <link href="https://jasontsang.dev/"/>
  <updated>2025-08-21T02:27:26.885Z</updated>
  <id>https://jasontsang.dev/</id>
  
  <author>
    <name>Jason Tsang</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Resume 我的简历</title>
    <link href="https://jasontsang.dev/resume/"/>
    <id>https://jasontsang.dev/resume/</id>
    <published>2025-08-21T01:23:40.000Z</published>
    <updated>2025-08-21T02:27:26.885Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Contact-Information"><a href="#Contact-Information" class="headerlink" title="Contact Information"></a>Contact Information</h1><ul><li>Email: <a href="mailto:&#106;&#x61;&#x73;&#x6f;&#110;&#116;&#x73;&#97;&#x6e;&#103;&#46;&#100;&#101;&#118;&#x40;&#103;&#x6d;&#97;&#x69;&#x6c;&#x2e;&#x63;&#x6f;&#x6d;">&#106;&#x61;&#x73;&#x6f;&#110;&#116;&#x73;&#97;&#x6e;&#103;&#46;&#100;&#101;&#118;&#x40;&#103;&#x6d;&#97;&#x69;&#x6c;&#x2e;&#x63;&#x6f;&#x6d;</a></li><li>WeChat: gnastnosaj</li><li>QQ: 2563745474</li></ul><hr><h1 id="Personal-Information"><a href="#Personal-Information" class="headerlink" title="Personal Information"></a>Personal Information</h1><ul><li>Jason Tsang &#x2F; Male &#x2F; 1991</li><li>Bachelor Degree &#x2F; Central South University</li><li>Major: Software Engineering</li><li>Working years: 12 years</li><li>Website：<a href="https://jasontsang.dev/">https://jasontsang.dev</a></li><li>GitHub：<a href="https://github.com/gnastnosaj">https://github.com/gnastnosaj</a></li></ul><hr><h1 id="Work-Experience"><a href="#Work-Experience" class="headerlink" title="Work Experience"></a>Work Experience</h1><h2 id="Joyflow-Inc-2024-12-Now"><a href="#Joyflow-Inc-2024-12-Now" class="headerlink" title="Joyflow.Inc (2024&#x2F;12 - Now)"></a>Joyflow.Inc (2024&#x2F;12 - Now)</h2><h3 id="GetMyHome"><a href="#GetMyHome" class="headerlink" title="GetMyHome"></a>GetMyHome</h3><p>GetMyHome is an app for home buyers with licensed real estate agents and cutting-edge AI. I was resposible for developing the app using Flutter framework and publishing the app on App Store. I was also engaged in the development of back-end services using Golang. GetMyHome is now avaiable on App Store and Google Play.</p><h2 id="PricewaterhouseCoopers-Information-Technologies-Shanghai-Co-Ltd-2022-04-2024-11"><a href="#PricewaterhouseCoopers-Information-Technologies-Shanghai-Co-Ltd-2022-04-2024-11" class="headerlink" title="PricewaterhouseCoopers Information Technologies (Shanghai) Co., Ltd (2022&#x2F;04 - 2024&#x2F;11)"></a>PricewaterhouseCoopers Information Technologies (Shanghai) Co., Ltd (2022&#x2F;04 - 2024&#x2F;11)</h2><h3 id="Smart-Rebuild"><a href="#Smart-Rebuild" class="headerlink" title="Smart Rebuild"></a>Smart Rebuild</h3><p>This project was build on micro service architecture, using Springboot and Angular framework, as a fullstack developer, I was resposible for user story efforts estimation, implementation and unit test for both front end and back end services based on the team capacity.</p><h3 id="FFG-Rebuild"><a href="#FFG-Rebuild" class="headerlink" title="FFG Rebuild"></a>FFG Rebuild</h3><p>This project was build on micro service architecture, using Springboot and Angular framework, I created a tool to transform angular js code to angular 18, I also import an angular library using git submodule which can save about 20% time of migration.</p><h2 id="Tiandi-Changzhou-Automation-Co-Ltd-2019-04-2022-03"><a href="#Tiandi-Changzhou-Automation-Co-Ltd-2019-04-2022-03" class="headerlink" title="Tiandi (Changzhou) Automation Co., Ltd (2019&#x2F;04 - 2022&#x2F;03)"></a>Tiandi (Changzhou) Automation Co., Ltd (2019&#x2F;04 - 2022&#x2F;03)</h2><h3 id="Belt-Conveyor-Control-Mobile-APP"><a href="#Belt-Conveyor-Control-Mobile-APP" class="headerlink" title="Belt Conveyor Control Mobile APP"></a>Belt Conveyor Control Mobile APP</h3><p>In this project, I was responsible for developing the belt conveyor control mobile app based on the ModBusTCP protocol and accessing the belt monitoring video based on the RTSP stream. This project was developed base on the Smart Mine Basic Information Platform For Mobile.</p><h3 id="Smart-Mine-Basic-Information-Platform-For-Mobile"><a href="#Smart-Mine-Basic-Information-Platform-For-Mobile" class="headerlink" title="Smart Mine Basic Information Platform For Mobile"></a>Smart Mine Basic Information Platform For Mobile</h3><p>In this project, I was responsible for creating the Smart Mine Basic Information Platform For Mobile based on the SCADA Software Framework, <a href="https://github.com/gnastnosaj/Boilerplate">Boilerplate</a>, and <a href="https://github.com/gnastnosaj/Boilerplate-cordova">Boilerplate-cordova</a>. I am also responsible for the development and maintance of basic modules and prodving the common solution of intergating with the platform for other subsystems.</p><h3 id="Product-Authorization-System"><a href="#Product-Authorization-System" class="headerlink" title="Product Authorization System"></a>Product Authorization System</h3><p>Product Authorization System was designed for preventing devices from being couterfeited. It contains an sdk invoked by business systems for communicating with the server of Product Authorization System, the sdk was developed using the Golang Programming Language and was compiled as a C shared Library. Another part was the server that providing the api for sdk and the database sync service, it was implemented with the NestJS framework. There was also a Javascript library built using Webpack for prodving a bridge between the sdk and the server in a specific network enviroment, and a management system for managing the contents that involved with the Product Authorization. I was acted as the project manager and the sole developer in this project. The system was delivered on time according to the quality.</p><h3 id="Smart-Mine-Basic-Information-Platform"><a href="#Smart-Mine-Basic-Information-Platform" class="headerlink" title="Smart Mine Basic Information Platform"></a>Smart Mine Basic Information Platform</h3><p>In this project, I was responsible for creating the Smart Mine Basic Information Platform based on the SCADA Software Framework, the development and maintance of basic modules and prodving the common solution of intergating with the platform for other subsystems.</p><h3 id="Report-Component"><a href="#Report-Component" class="headerlink" title="Report Component"></a>Report Component</h3><p>I was responsible for system architecture and development of the project, it was implemented with the Angular framework and the datasource was implemented with GraphQL. The Project used the interactive design of some commercial softwares for reference such as FineReport and Huozige, and implemented functionalities such as data expansion, data association, data grouping and spreadsheet design.</p><h3 id="Software-Authorization-Component"><a href="#Software-Authorization-Component" class="headerlink" title="Software Authorization Component"></a>Software Authorization Component</h3><p>I was responsible for system architecture of the project and development of the management system. It includes functionalities such as offline authorization, online authorization, licenses management. It has improved my ability of business analysis, software design and system architecture.</p><h3 id="Software-Update-Component"><a href="#Software-Update-Component" class="headerlink" title="Software Update Component"></a>Software Update Component</h3><p>I was responsible for system architecture of the project and development of the client and the management system. The client was implemented with Electron and shell, it includes functionalities such as running as daemon, executing the update strategies, pretreatment of upgrade and post-treatment of upgrade. The management system was implemented with the Angular framework for managing the update strategies, it could be integrated with the client to view the logs.</p><h3 id="SCADA-Software-Framework"><a href="#SCADA-Software-Framework" class="headerlink" title="SCADA Software Framework"></a>SCADA Software Framework</h3><p>I was responsible for construting the front-end framework based on Angular, the framework includes an event bus library named rxbus, an animiation library based on lottie, and a dynamic theme render library. I learned from the NG-ZORRO project and created a repo which includes business components gain from projects, so that the developers can reuse the components in the repo and contribute to the repo.</p><h3 id="Others"><a href="#Others" class="headerlink" title="Others"></a>Others</h3><p>Gitlab Issues Analysis (based on Angular), Nexus Repository Manager Private Package List (based on Groovy and Angular), Management of Micro Services (based on Angular).</p><h2 id="Jiangsu-Guoguang-Information-Industry-Co-Ltd-2013-07-2019-03"><a href="#Jiangsu-Guoguang-Information-Industry-Co-Ltd-2013-07-2019-03" class="headerlink" title="Jiangsu Guoguang Information Industry Co., Ltd (2013&#x2F;07 - 2019&#x2F;03)"></a>Jiangsu Guoguang Information Industry Co., Ltd (2013&#x2F;07 - 2019&#x2F;03)</h2><h3 id="Control-System-Of-The-Bank-Queuing-Machines"><a href="#Control-System-Of-The-Bank-Queuing-Machines" class="headerlink" title="Control System Of The Bank Queuing Machines"></a>Control System Of The Bank Queuing Machines</h3><p>I was responsible for developing the system on Android which is used to control the wireless devices placed in the bank, such as window panes, primary pane and wireless speakers and provide restful api for other business systems. The main problems were communication protocol and encoding conversion between different platforms, serial port communication with wireless devices, announcements implemented with audiotrack api, switching between master and slave services, concurrency issues, sequential issues and performance. This project was implemented by using the Kotlin programming language, the performance of the system is closed to the one on Windows OS.</p><h3 id="Hubei-Bank-Mobile-Marketing-rebuild"><a href="#Hubei-Bank-Mobile-Marketing-rebuild" class="headerlink" title="Hubei Bank Mobile Marketing rebuild"></a>Hubei Bank Mobile Marketing rebuild</h3><p>In this project, I was responsible for the rebuild of the android client and the development of some new businesses. Through the application of dynamic hook technology, the optimization and perfection of multiple functions are completed under the demand of reducing the modification of source code as much as possible. I didn’t participate in the early project development before, but I can quickly master the project architecture and complete the corresponding modifications with minimal intrusion.</p><h3 id="Liaoning-Rural-Credit-Cooperative-Mobile-Marketing"><a href="#Liaoning-Rural-Credit-Cooperative-Mobile-Marketing" class="headerlink" title="Liaoning Rural Credit Cooperative Mobile Marketing"></a>Liaoning Rural Credit Cooperative Mobile Marketing</h3><p>In this project, I was responsible for the architecture design and coding of android client and business system based on HTML5(<code>Cordova + member. JS</code>). According to the business characteristics of the bank, I have developed multiple business templates to facilitate the maintenance personnel to configure based on the business template according to the needs to complete the secondary development of subsequent businesses. Deepened my understanding of hybrid application architecture development.</p><h3 id="Internet-Identity-Authentication-of-Changchun-Administration-for-Industry-and-Commerce"><a href="#Internet-Identity-Authentication-of-Changchun-Administration-for-Industry-and-Commerce" class="headerlink" title="Internet Identity Authentication of Changchun Administration for Industry and Commerce"></a>Internet Identity Authentication of Changchun Administration for Industry and Commerce</h3><p>In this project, I was responsible for the development of apps under Android and IOS(<code>Kotlin and Swift respectively</code>), the development of background micro services(<code>Kotlin，Blade</code>). This project enables me to have a deep understanding of kotlin and swift languages, and achieve a high degree of similarity and portability between app source codes under Android and IOS. In a similar situation, I personally worked with friends on an outsourced application, <code>Miquaner</code>. I am also responsible for the app architecture and main coding under Android and IOS. At present, it is downloaded in major application markets and Apple store.</p><h3 id="Bidding-of-Anhui-Rural-Credit-Cooperative-Android-Tablet"><a href="#Bidding-of-Anhui-Rural-Credit-Cooperative-Android-Tablet" class="headerlink" title="Bidding of Anhui Rural Credit Cooperative Android Tablet"></a>Bidding of Anhui Rural Credit Cooperative Android Tablet</h3><p>In this project, I was responsible for developing the system interface required by the bidding.Because the tablet provided by the company is purchased from a third party, the tablet system cannot be customized according to the requirements of the bank. In this case, I thought of completing the corresponding interface development through the transformation of xposed(<code>an application that needs root and replaces some core programs of the system to realize hook, so as to change the system logic</code>) and the preparation of plug-ins, but the test application puts forward requirements for system security, The device is required not to allow root, and then I blocked the interface obtained by root permission through hook, so that the test application and other applications cannot obtain the root permission of the device(<code>only the application providing the interface can obtain root permission</code>), and passed the test successfully. Although the subsequent bidding failed to be implemented for various reasons, this project deepened my understanding of Android system and reflected my self-study ability and research spirit.</p><h3 id="Dandong-Bank-Intelligent-Counter"><a href="#Dandong-Bank-Intelligent-Counter" class="headerlink" title="Dandong Bank Intelligent Counter"></a>Dandong Bank Intelligent Counter</h3><p>I was responsible for writing HTML5 business code in this project, which mainly involves business logic. The logic of cash business gives me the most profound image. Deposit and withdrawal is not as simple as it is written. It includes a lot of logic for interacting with devices and recovering exceptions, which not only deepens my understanding of logic and process, but also has a certain grasp of JavaScript ES6 syntax.</p><h3 id="Sichuan-Rural-Credit-Cooperative-Centralized-Authorization-Phase-II"><a href="#Sichuan-Rural-Credit-Cooperative-Centralized-Authorization-Phase-II" class="headerlink" title="Sichuan Rural Credit Cooperative Centralized Authorization Phase II"></a>Sichuan Rural Credit Cooperative Centralized Authorization Phase II</h3><p>In this project, I was responsible for the rebuild and implementation of Sichuan Rural Credit Cooperative Centralized Authorization client and background management system. This was my first project after joining the company, and I had a deep understanding of Linux server deployment and implementation. The centralized authorization servers of rural credit cooperatives in all prefectures and cities in Sichuan Province were finally automatically installed from the RedHat system installation package customized by me(<code>using the kickstart of RedHat for customization. All servers in phase I were deployed by operations person one by one. Of course, internet companies may choose docker as the solution, today.</code>).</p><h3 id="Others-1"><a href="#Others-1" class="headerlink" title="Others"></a>Others</h3><p>Internet Trusted Identity Authentication(<code>Android and iOS</code>), Mobile Recording(<code>Android</code>), Universal Bluetooth Library(<code>spp and ble</code>), Mobile Phone Hearing Aid(<code>Android</code>), drivers of various mobile financial peripherals(<code>Android</code>).</p><h1 id="Open-Source-Projects-and-Works"><a href="#Open-Source-Projects-and-Works" class="headerlink" title="Open Source Projects and Works"></a>Open Source Projects and Works</h1><h2 id="Open-Source-Projects"><a href="#Open-Source-Projects" class="headerlink" title="Open Source Projects"></a>Open Source Projects</h2><ul><li><a href="https://github.com/gnastnosaj/Filter">Filter</a>: Filter is a plug-in crawler application in the form of DSL syntax inspired by the DSL syntax of Gradle. Users can write groovy scripts to fetch data from favorite websites and display it in the application. Filter provides several general layouts. The newest <a href="https://www.pgyer.com/D7r4">Filter</a> was implemented with Kotlin. </li><li><a href="https://github.com/gnastnosaj/Boilerplate">Boilerplate</a>: As the name suggests, Boilerplate is a development template. It is some common things accumulated and summarized from daily development, which is convenient to quickly start the development of Android applications.<a href="https://github.com/gnastnosaj/Boilerplate-util">Boilerplate-util</a>, <a href="https://github.com/gnastnosaj/Boilerplate-ipc">Boilerplate-ipc</a> and <a href="https://github.com/gnastnosaj/Boilerplate-cordova">Boilerplate-cordova</a> are also commonly used development templates.</li><li><a href="https://jasontsang.dev/lab">Lab</a>: This project is a front-end project developed in the process of angular learning, including some common components and features of angular developed by myself. A client based on electron is provided.</li></ul><h2 id="Works"><a href="#Works" class="headerlink" title="Works"></a>Works</h2><ul><li><a href="https://www.25pp.com/android/detail_7680585/">Miquaner</a>: A Taoke app that supports Android and iOS.</li><li><a href="https://jxjsh.gitee.io/design/">Jixiangjia Customization</a>: A roller shutters custom APP, support Web, WeChat mini program, Alipay mini program.</li><li><a href="https://tidytech.evozic.com/">tidytech</a>: A smart storage app based on flutter that supports Web, Android and iOS.</li><li><a href="https://www.jasontsang.dev:4096/petdoor">petdoor</a>: A web app based on flutter used for customizing petdoor which can import 3D model with three.js</li></ul><h1 id="Skills"><a href="#Skills" class="headerlink" title="Skills"></a>Skills</h1><p>The following are the skills that I have mastered and used</p><ul><li>Web Development: Java&#x2F;Node&#x2F;Golang</li><li>Web Framework: Spring&#x2F;NestJS&#x2F;Gin</li><li>Front-End Framework: Angular&#x2F;Vue.js&#x2F;React</li><li>Front-End Toolkit: NPM&#x2F;Less&#x2F;Gulp</li><li>Mobile Development: Kotlin&#x2F;Swift&#x2F;Dart&#x2F;Flutter&#x2F;Cordova&#x2F;WeChat Official Account&#x2F;WeChat mini program&#x2F;Alipay mini program</li><li>Database: MySQL&#x2F;SQLite&#x2F;Oracle&#x2F;DB2</li><li>Version Control and Deployment: Git&#x2F;Travis</li><li>Unit Test: JUnit</li><li>Cloud and open platform: WeChat Official Accounts Platform&#x2F;Alipay open platform&#x2F;Tencent Cloud&#x2F;Baidu Cloud&#x2F;Azure&#x2F;GCP</li></ul><h1 id="Thanks"><a href="#Thanks" class="headerlink" title="Thanks"></a>Thanks</h1><p>Thank you for taking the time to read my resume and look forward to the opportunity to work with you.</p><h1 id="联系方式"><a href="#联系方式" class="headerlink" title="联系方式"></a>联系方式</h1><ul><li>Email：<a href="mailto:&#106;&#x61;&#115;&#x6f;&#x6e;&#x74;&#115;&#x61;&#x6e;&#x67;&#46;&#100;&#101;&#x76;&#x40;&#103;&#x6d;&#x61;&#105;&#108;&#46;&#99;&#x6f;&#x6d;">&#106;&#x61;&#115;&#x6f;&#x6e;&#x74;&#115;&#x61;&#x6e;&#x67;&#46;&#100;&#101;&#x76;&#x40;&#103;&#x6d;&#x61;&#105;&#108;&#46;&#99;&#x6f;&#x6d;</a></li><li>微信：gnastnosaj</li><li>QQ：2563745474</li></ul><hr><h1 id="个人信息"><a href="#个人信息" class="headerlink" title="个人信息"></a>个人信息</h1><ul><li>曾健&#x2F;男&#x2F;1991</li><li>本科&#x2F;中南大学</li><li>专业：软件工程</li><li>工作年限：11年</li><li>技术博客：<a href="https://jasontsang.dev/">https://jasontsang.dev</a></li><li>GitHub：<a href="https://github.com/gnastnosaj">https://github.com/gnastnosaj</a></li></ul><hr><h1 id="工作经历"><a href="#工作经历" class="headerlink" title="工作经历"></a>工作经历</h1><h2 id="Joyflow-Inc-2024年11月-至今"><a href="#Joyflow-Inc-2024年11月-至今" class="headerlink" title="Joyflow.Inc (2024年11月 ~ 至今)"></a>Joyflow.Inc (2024年11月 ~ 至今)</h2><h3 id="GetMyHome-1"><a href="#GetMyHome-1" class="headerlink" title="GetMyHome"></a>GetMyHome</h3><p>GetMyHome是一款使用AI技术帮助购房者经历更好的购房体验的产品，我主要负责移动端应用从零的开发、发布以及维护，同时也参与到后端开发。移动端使用Flutter、后端使用Golang，目前GetMyHome已经在App Store和Google Play上发布。</p><h2 id="普华永道信息技术（上海）有限公司-2022年4月-2024年11月"><a href="#普华永道信息技术（上海）有限公司-2022年4月-2024年11月" class="headerlink" title="普华永道信息技术（上海）有限公司 (2022年4月 ~ 2024年11月)"></a>普华永道信息技术（上海）有限公司 (2022年4月 ~ 2024年11月)</h2><h3 id="Smart-Rebuild-1"><a href="#Smart-Rebuild-1" class="headerlink" title="Smart Rebuild"></a>Smart Rebuild</h3><p>此项目采用微服务架构，使用Springboot和Angular框架，作为一个全栈开发，我主要负责User Story的工作量评估、功能实现以及单元测试。</p><h3 id="FFG-Rebuild-1"><a href="#FFG-Rebuild-1" class="headerlink" title="FFG Rebuild"></a>FFG Rebuild</h3><p>此项目采用微服务架构，使用Springboot和Angular框架，我为项目开发了一个移植工具，将原来的AngularJS代码转化为Angular代码，同时引入了Angular Library结合Git Submodule实现多个子应用间的代码共享，节省了大约20%的工作量。</p><h2 id="天地自动化股份有限公司-（2019年4月-2022年3月）"><a href="#天地自动化股份有限公司-（2019年4月-2022年3月）" class="headerlink" title="天地自动化股份有限公司 （2019年4月 ~ 2022年3月）"></a>天地自动化股份有限公司 （2019年4月 ~ 2022年3月）</h2><h3 id="胶带机控制移动端"><a href="#胶带机控制移动端" class="headerlink" title="胶带机控制移动端"></a>胶带机控制移动端</h3><p>我在此项目负责开发基于ModbusTCP协议的胶带机控制移动端程序、以及基于RTSP流的皮带监控视频的接入，该项目基于智慧矿山基础信息平台移动端开发。</p><h3 id="智慧矿山基础信息平台移动端"><a href="#智慧矿山基础信息平台移动端" class="headerlink" title="智慧矿山基础信息平台移动端"></a>智慧矿山基础信息平台移动端</h3><p>我在此项目负责基于监控类软件框架、<a href="https://github.com/gnastnosaj/Boilerplate">Boilerplate</a>、<a href="https://github.com/gnastnosaj/Boilerplate-cordova">Boilerplate-cordova</a>，搭建智慧矿山基础信息平台移动端框架，负责开发维护基础模块，并提供其他子系统接入平台的统一方案。</p><h3 id="产品授权系统"><a href="#产品授权系统" class="headerlink" title="产品授权系统"></a>产品授权系统</h3><p>产品授权系统负责公司产品的防伪验证，主要模块包括提供给其他系统的sdk（采用golang编写，导出为c shared library）、负责与sdk交互以及与公司产品生命周期相关系统的数据同步（基于Nestjs）、用于查看和管理产品授权相关内容的管理系统（基于Angular）以及负责在特定网络环境协助sdk和服务器交互的javascript库（webpack）。</p><h3 id="智慧矿山基础信息平台"><a href="#智慧矿山基础信息平台" class="headerlink" title="智慧矿山基础信息平台"></a>智慧矿山基础信息平台</h3><p>我在此项目负责基于监控类软件框架搭建智慧矿山基础信息平台前端框架，负责开发维护基础模块，并提供其他子系统接入平台的统一方案。</p><h3 id="报表组件"><a href="#报表组件" class="headerlink" title="报表组件"></a>报表组件</h3><p>我在此项目负责整体系统设计、开发，采用Angular开发框架引入GraphQL作为报表数据源交互方式实现自定义报表单元格等常见功能，借鉴帆软报表、活字格等商业软件的交互设计，实现数据扩展、关联分组和表单设计等扩展功能。</p><h3 id="软件许可授权功能组件"><a href="#软件许可授权功能组件" class="headerlink" title="软件许可授权功能组件"></a>软件许可授权功能组件</h3><p>我在此项目负责系统设计和管理端的开发，包括离线许可授权、在线许可授权、许可授权管理等功能。提升了对项目的需求分析、设计以及系统架构的能力。</p><h3 id="软件在线更新功能组件"><a href="#软件在线更新功能组件" class="headerlink" title="软件在线更新功能组件"></a>软件在线更新功能组件</h3><p>我在此项目负责系统设计、客户端以及管理端开发，客户端部分使用Electron框架结合批处理脚本实现后台系统常驻、在线更新策略执行，更新预处理、后处理等通用在线更新功能。管理端基于Angular框架实现更新策略管理等功能，同时也能运行在客户端中进行本地的日志查看。项目采用了非平台原生架构，能在满足需求的同时保持较高的质量。</p><h3 id="监控类软件框架"><a href="#监控类软件框架" class="headerlink" title="监控类软件框架"></a>监控类软件框架</h3><p>我在此项目负责基于Angular的前端框架和UI搭建，封装包括基于rxbus思想的事件总线库、基于lottie的动画组件库、基于less的动态主题渲染库以及一些常用的工具和组件。同时参考NG-ZORRO官网，搭建了一套业务组件库的开发框架，开发成员可以针对公司的业务更细化的进行业务组件的开发和重用，使用markdown进行组件文档编写并且动态生成组件库官网。此项目中我充分借鉴了已有的移动端开发经验，确保了开发效率和项目质量。</p><h3 id="其他项目"><a href="#其他项目" class="headerlink" title="其他项目"></a>其他项目</h3><p>其他开发的项目包括GitLab项目看板统计（Angular）、Nexus Repository Manager私有库列表（Groovy、Angular）、微服务管理平台（Angular）。</p><h2 id="江苏国光信息产业股份有限公司-（2013年7月-2019年3月）"><a href="#江苏国光信息产业股份有限公司-（2013年7月-2019年3月）" class="headerlink" title="江苏国光信息产业股份有限公司 （2013年7月 ~ 2019年3月）"></a>江苏国光信息产业股份有限公司 （2013年7月 ~ 2019年3月）</h2><h3 id="排队叫号机安卓控制盒服务"><a href="#排队叫号机安卓控制盒服务" class="headerlink" title="排队叫号机安卓控制盒服务"></a>排队叫号机安卓控制盒服务</h3><p>我在此项目负责叫号机安卓控制盒服务的开发，控制盒服务主要负责控制无线设备包括窗口屏、综合显示屏、呼叫器、无线音箱等设备的工作，同时对外统一提供排队叫号的接口。该项目需要解决各平台之间的通讯协议、编码等转换问题，无线设备的串口操作、本地语音的高效播放（基于android的audiotrack流播放）、主从控制盒切换以及实现业务逻辑的高并发和时序队列等性能和功能要求。该项目使用kotlin语言开发完成，性能接近基于windows系统的控制盒服务，在此之前公司还未有在安卓平台下的相关产品。</p><h3 id="湖北银行移动营销二期改造"><a href="#湖北银行移动营销二期改造" class="headerlink" title="湖北银行移动营销二期改造"></a>湖北银行移动营销二期改造</h3><p>我在此项目负责安卓客户端主程序、部分业务子程序的改造，以及部分新增业务的开发。通过应用内动态hook技术，在尽可能减少源代码修改的情况下，完成多处功能的优化和完善。之前并未参与前期的项目开发，但是能快速掌握项目架构并以最小的侵入方式完成对应的修改。</p><h3 id="辽宁农信移动营销"><a href="#辽宁农信移动营销" class="headerlink" title="辽宁农信移动营销"></a>辽宁农信移动营销</h3><p>我在此项目负责安卓客户端主程序以及基于html5实现的业务系统的架构设计和编码(<code>cordova+ember.js</code>)，根据银行的业务特征开发了多个业务模板，方便维护人员根据需求基于业务模板进行配置来完成后续业务的二次开发。加深了我对hybrid应用架构开发的认识。</p><h3 id="长春工商局互联网身份认证"><a href="#长春工商局互联网身份认证" class="headerlink" title="长春工商局互联网身份认证"></a>长春工商局互联网身份认证</h3><p>我在此项目负责Android和iOS下的app开发(<code>Android下采用最新的kotlin语言、iOS下采用最新的swift语言</code>)以及后台微服务的开发(<code>微服务采用的kotlin语言，使用类似springboot的超轻量框架blade</code>)，此项目使我对kotlin和swift语言有了较深的认识，同时做到Android和iOS下app源代码之间的高度相似和高度可移植性(<code>原生层面上的架构、语言共性等，未采用任何跨平台框架技术</code>)。类似的情况，我个人还和朋友合作过一个外包的应用，觅券儿，也是由我负责Android和iOS下的app架构和主要编码，目前在各大应用市场和Apple Store均有下载。</p><h3 id="安徽农信安卓平板招标测试"><a href="#安徽农信安卓平板招标测试" class="headerlink" title="安徽农信安卓平板招标测试"></a>安徽农信安卓平板招标测试</h3><p>我在此项目负责安徽农信安卓平板招标测试配合，负责开发招标要求的系统接口。由于公司提供给的平板采购于第三方的原因，平板系统并不能按照银行要求进行接口定制，在此情况下，我想到通过对xposed的改造(<code>一款需要root并替换系统部分核心程序来实现hook从而改变系统逻辑的应用</code>)以及插件的编写完成了相应的接口开发，但测试应用对系统安全性又提出要求，要求设备不允许root，之后我又通过hook将root权限获取的接口封死，使测试应用和其他应用都无法获取到设备的root权限(<code>只有提供接口的应用可以获取root权限</code>)，并顺利通过测试。虽然后续招标因为种种原因未能落地，但这个项目加深了我对Android系统层面的认识，也体现了我的自学能力和钻研精神。</p><h3 id="丹东银行智能柜台"><a href="#丹东银行智能柜台" class="headerlink" title="丹东银行智能柜台"></a>丹东银行智能柜台</h3><p>我在此项目负责html5业务代码的编写，主要涉及的都是业务逻辑，其中关于现金业务的逻辑给我的映象最为深刻，存取款并不像字面那么简单，其中包含了大量和设备交互以及恢复异常的逻辑，加深了我对逻辑和流程的认识，同时也对JavaScript ES6语法有了一定的掌握。</p><h3 id="四川农信集中授权二期"><a href="#四川农信集中授权二期" class="headerlink" title="四川农信集中授权二期"></a>四川农信集中授权二期</h3><p>我在此项目负责四川农信集中授权客户端和后台管理系统的改造以及实施。这是我加入公司的第一个项目，对linux服务器部署实施有了较深的认识，整个四川省所有地市农信社的集中授权服务器最后都是由我定制的redhat系统安装包自动安装的(<code>采用redhat的kickstart进行定制，一期的所有服务器都是前辈一个地市一个地市跑去部署，当然现在互联网公司可能会选择docker这样的方案</code>)。</p><h3 id="其他项目-1"><a href="#其他项目-1" class="headerlink" title="其他项目"></a>其他项目</h3><p>其他开发的项目包括互联网可信身份认证(<code>Android和iOS</code>)、移动录音录像(<code>Android</code>)、通用蓝牙库(<code>支持spp和ble</code>)、手机助听器(<code>Android</code>)、以及各类移动金融外设驱动开发(<code>Android</code>)</p><h1 id="开源项目和作品"><a href="#开源项目和作品" class="headerlink" title="开源项目和作品"></a>开源项目和作品</h1><h2 id="开源项目"><a href="#开源项目" class="headerlink" title="开源项目"></a>开源项目</h2><ul><li><a href="https://github.com/gnastnosaj/Filter">Filter</a>：灵感源自于gradle构建器的dsl语法，Filter是一款通过采用dsl语法的形式来实现插件化的爬虫类应用，用户可以自行编写groovy脚本来获取喜爱的网站数据并在应用中展示，Filter提供了几种通用的布局展示。</li><li><a href="https://github.com/gnastnosaj/Boilerplate">Boilerplate</a>：Boilerplate顾名思义就是开发模板，是日常开发中积累和总结出来的一些通用的东西，便于快速的开始进行安卓应用的开发。另外<a href="https://github.com/gnastnosaj/Boilerplate-util">Boilerplate-util</a>、<a href="https://github.com/gnastnosaj/Boilerplate-ipc">Boilerplate-ipc</a>、<a href="https://github.com/gnastnosaj/Boilerplate-cordova">Boilerplate-cordova</a>也是几个常用的开发模板。</li><li><a href="https://jasontsang.dev/lab">Lab</a>：此项目是在Angular学习过程中开发的一个前端项目，包含了自己开发的一些Angular通用组件和特性，基于Electron封装客户端并提供下载。</li></ul><h2 id="作品"><a href="#作品" class="headerlink" title="作品"></a>作品</h2><ul><li><a href="https://www.25pp.com/android/detail_7680585/">觅券儿</a>：一款淘客类APP，支持Android和iOS</li><li><a href="https://jxjsh.gitee.io/design/">吉祥家私人定制</a>：一款卷帘定制APP，支持Web、微信小程序、支付宝小程序</li><li><a href="https://tidytech.evozic.com/">tidytech</a>: 一款基于Flutter制作的智能收纳APP，支持Web、Android、iOS</li><li><a href="https://www.jasontsang.dev:4096/petdoor">petdoor</a>: 一款基于Flutter制作的宠物门定制APP，使用three.js支持3D模型导入</li></ul><h1 id="技能清单"><a href="#技能清单" class="headerlink" title="技能清单"></a>技能清单</h1><p>以下均为我熟练使用的技能</p><ul><li>Web开发：Java&#x2F;Node&#x2F;Golang</li><li>Web框架：Spring&#x2F;NestJS&#x2F;Gin</li><li>前端框架：Angular&#x2F;Vue.js&#x2F;React</li><li>前端工具：NPM&#x2F;Less&#x2F;Gulp</li><li>移动开发：Kotlin&#x2F;Swift&#x2F;Dart&#x2F;Flutter&#x2F;Cordova&#x2F;微信公众号&#x2F;微信小程序&#x2F;支付宝小程序</li><li>数据库相关：MySQL&#x2F;SQLite&#x2F;Oracle&#x2F;DB2</li><li>版本管理、文档和自动化部署工具：Git&#x2F;Travis</li><li>单元测试：JUnit</li><li>云和开放平台：微信公众平台&#x2F;支付宝开放平台&#x2F;腾讯云&#x2F;百度云&#x2F;Azure&#x2F;GCP</li></ul><h1 id="致谢"><a href="#致谢" class="headerlink" title="致谢"></a>致谢</h1><p>感谢您花时间阅读我的简历，期待能有机会和您共事。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Contact-Information&quot;&gt;&lt;a href=&quot;#Contact-Information&quot; class=&quot;headerlink&quot; title=&quot;Contact Information&quot;&gt;&lt;/a&gt;Contact Information&lt;/h1&gt;&lt;ul&gt;
</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>RxBus与Angular</title>
    <link href="https://jasontsang.dev/angular-rxbus/"/>
    <id>https://jasontsang.dev/angular-rxbus/</id>
    <published>2019-10-16T01:44:05.000Z</published>
    <updated>2024-10-09T01:35:51.568Z</updated>
    
    <content type="html"><![CDATA[<h1 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h1><p>笔者在参与Angular项目开发过程中常遇到需要解决组件之间、父子页之间的通信问题，为了降低通信复杂度，以及联想到此前参与过的Android项目开发经验，考虑引进事件总线的解决方案，而由于Angular自带RxJS，故开发一个基于RxJS版本的RxBus成为首选。</p><h1 id="RxBus"><a href="#RxBus" class="headerlink" title="RxBus"></a>RxBus</h1><p>RxBus的核心思想是基于Rx实现的事件发布与订阅，网上有许多相关资料，笔者这里就不做详细介绍，下面是笔者根据RxBus核心思想用ts语言编写的RxBus源代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">import &#123; Subject, Observable &#125; from &#x27;rxjs&#x27;;</span><br><span class="line">import &#123; Injectable &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line"></span><br><span class="line">@Injectable(&#123;</span><br><span class="line">    providedIn: &#x27;root&#x27;</span><br><span class="line">&#125;)</span><br><span class="line">export class RxBus &#123;</span><br><span class="line">    bus: Subject&lt;any&gt; = new Subject();</span><br><span class="line">    subjectMapper: &#123;</span><br><span class="line">        [propName: string]: Array&lt;Subject&lt;any&gt;&gt;;</span><br><span class="line">    &#125; = &#123;&#125;;</span><br><span class="line"></span><br><span class="line">    toObserverable(): Observable&lt;any&gt; &#123;</span><br><span class="line">        return this.bus;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    send(object: any) &#123;</span><br><span class="line">        this.bus.next(object);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    register&lt;T&gt;(tag: any): Observable&lt;T&gt; &#123;</span><br><span class="line">        const subject = new Subject&lt;T&gt;();</span><br><span class="line">        let subjectList = this.subjectMapper[tag];</span><br><span class="line">        if (subjectList == null) &#123;</span><br><span class="line">            subjectList = new Array&lt;Subject&lt;T&gt;&gt;();</span><br><span class="line">            this.subjectMapper[tag] = subjectList;</span><br><span class="line">        &#125;</span><br><span class="line">        subjectList.push(subject);</span><br><span class="line">        return subject;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    unregister(tag: any, observable: Observable&lt;any&gt;) &#123;</span><br><span class="line">        const subjectList = this.subjectMapper[tag];</span><br><span class="line">        if (subjectList != null) &#123;</span><br><span class="line">            this.subjectMapper[tag] = subjectList.filter(subject =&gt; subject !== observable);</span><br><span class="line"></span><br><span class="line">            if (this.subjectMapper[tag].length === 0) &#123;</span><br><span class="line">                delete this.subjectMapper[tag];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    post&lt;T&gt;(tag: any, object: T) &#123;</span><br><span class="line">        const subjectList = this.subjectMapper[tag];</span><br><span class="line">        if (subjectList != null) &#123;</span><br><span class="line">            subjectList.forEach(subject =&gt; subject.next(object));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h1><p>笔者以iframe子页调用父页Angular生命周期中的函数为例加以说明</p><ol><li>在Angular应用启动时，将RxBus事件总线加入到window全局变量中，便于iframe子页以top.window.rxbus的形式调用 <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">//app.component.ts</span><br><span class="line">...</span><br><span class="line">//通过构造函数注入RxBus事件总线</span><br><span class="line">constructor(public rxbus: RxBus, private ngZone: NgZone, ...) &#123;</span><br><span class="line">    ...</span><br><span class="line">    (window as any).rxbus = this.rxbus;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure></li><li>在RxBus中注册事件并订阅 <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">this.rxbus.register&lt;any&gt;(&#x27;graph&#x27;).subscribe(data =&gt; &#123;</span><br><span class="line">    //需要通过NgZone将调用作用域加入到Angular生命周期中</span><br><span class="line">    this.ngZone.run(() =&gt; &#123;</span><br><span class="line">        ...</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line">...</span><br></pre></td></tr></table></figure></li><li>在iframe子页中发布事件 <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">    top.window.rxbus.post(&#x27;graph&#x27;, &#123;</span><br><span class="line">        tag: &#x27;&#x27;,</span><br><span class="line">        payload: &#123;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">...</span><br></pre></td></tr></table></figure>通过事件传递的数据结构开发者可以根据实际情况自行约定</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;引言&quot;&gt;&lt;a href=&quot;#引言&quot; class=&quot;headerlink&quot; title=&quot;引言&quot;&gt;&lt;/a&gt;引言&lt;/h1&gt;&lt;p&gt;笔者在参与Angular项目开发过程中常遇到需要解决组件之间、父子页之间的通信问题，为了降低通信复杂度，以及联想到此前参与过的Android</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Angular主题方案</title>
    <link href="https://jasontsang.dev/angular-theme/"/>
    <id>https://jasontsang.dev/angular-theme/</id>
    <published>2019-05-28T01:44:05.000Z</published>
    <updated>2024-10-09T01:35:51.568Z</updated>
    
    <content type="html"><![CDATA[<h1 id="基于NgClass的主题切换"><a href="#基于NgClass的主题切换" class="headerlink" title="基于NgClass的主题切换"></a>基于NgClass的主题切换</h1><p>Angular常见的主题方案是将组件设置NgClass属性并动态绑定主题class，通过编写对应class的样式以及切换class来达到切换主题的目的。为了简化实践，我们可以将此过程封装成一个Directive。</p><p>参考代码如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">import &#123; BehaviorSubject &#125; from &#x27;rxjs&#x27;;</span><br><span class="line"></span><br><span class="line">//主题Subject</span><br><span class="line">const theme: BehaviorSubject&lt;string&gt; = new BehaviorSubject(null);</span><br><span class="line"></span><br><span class="line">//所有主题数组</span><br><span class="line">const themes: Array&lt;string&gt; = [];</span><br><span class="line"></span><br><span class="line">//订阅主题Subject，将主题class应用到body</span><br><span class="line">theme.subscribe(c =&gt; &#123;</span><br><span class="line">    themes.forEach(t =&gt; &#123;</span><br><span class="line">        if (t === c) &#123;</span><br><span class="line">            document.body.classList.add(t);</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            document.body.classList.remove(t);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">//注册主题</span><br><span class="line">function registerThemes(...themesToRegister: Array&lt;string&gt;) &#123;</span><br><span class="line">    themes.push(...themesToRegister);</span><br><span class="line">    if (theme.value == null) &#123;</span><br><span class="line">        theme.next(themesToRegister[0]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">import &#123; Directive, ElementRef &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line"></span><br><span class="line">//主题Directive，将主题class应用到对应元素</span><br><span class="line">@Directive(&#123;</span><br><span class="line">    selector: &#x27;[ngCariTheme]&#x27;</span><br><span class="line">&#125;)</span><br><span class="line">export class ThemeDirective &#123;</span><br><span class="line">    constructor(el: ElementRef) &#123;</span><br><span class="line">        theme.subscribe(c =&gt; &#123;</span><br><span class="line">            themes.forEach(t =&gt; &#123;</span><br><span class="line">                if (t === c) &#123;</span><br><span class="line">                    el.nativeElement.classList.add(t);</span><br><span class="line">                &#125; else &#123;</span><br><span class="line">                    el.nativeElement.classList.remove(t);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用方式</p><ol><li>应用初始化时调用registerThemes注册所有主题</li><li>在对应的组件上添加ngCariTheme属性来绑定主题class</li><li>编写对应class样式</li><li>最后通过theme.next(‘theme’)切换主题。</li></ol><h1 id="NG-ZORRO主题切换"><a href="#NG-ZORRO主题切换" class="headerlink" title="NG-ZORRO主题切换"></a>NG-ZORRO主题切换</h1><p>基于NgClass的主题切换虽然能实现我们想要的效果，但是想要修改整体效果却需要耗费很大的工作量，考虑到目前项目中使用了NG-ZORRO组件与样式库，可以采用先切换NG-ZORRO主题再调整部分主题细节的方案。</p><p>实现整体切换NG-ZORRO主题有两种方案，第一种是预先将多种主题less编译成css并以静态资源的方式来进行打包处理，在应用加载时动态加载对应主题的css，第二种方案是将多种主题的less文件以静态资源的方式来进行打包处理，在应用加载时动态编译less并应用到全局样式。考虑到我们目前的项目最终以C的形态呈现，不存在网络延迟导致静态资源加载速度过慢的问题，所以采用第二种方案，以达到简便灵活的目的。</p><p>参考代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">//主题节点元素</span><br><span class="line">style = null;</span><br><span class="line">//缓存主题css样式</span><br><span class="line">styles = &#123;&#125;;</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">//订阅主题切换</span><br><span class="line">theme.subscribe(t =&gt; &#123;</span><br><span class="line">  if (t != null) &#123;</span><br><span class="line">    //创建主题节点元素</span><br><span class="line">    if (this.style == null) &#123;</span><br><span class="line">      this.style = document.createElement(&#x27;style&#x27;);</span><br><span class="line">      this.style.setAttribute(&#x27;type&#x27;, &#x27;text/css&#x27;);</span><br><span class="line">      document.head.appendChild(this.style);</span><br><span class="line">    &#125;</span><br><span class="line">    if (this.styles[t]) &#123;</span><br><span class="line">      //应用缓存主题css样式</span><br><span class="line">      this.style.innerHTML = this.styles[t];</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      //动态编译主题less并缓存</span><br><span class="line">      less.render(`@import &quot;/theme/$&#123;t&#125;.less&quot;;`, &#123;</span><br><span class="line">        javascriptEnabled: true</span><br><span class="line">      &#125;).then(output =&gt; &#123;</span><br><span class="line">        this.style.innerHTML = output.css;</span><br><span class="line">        this.styles[t] = output.css;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">//注册主题</span><br><span class="line">registerThemes(&#x27;light&#x27;, &#x27;dark&#x27;);</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;基于NgClass的主题切换&quot;&gt;&lt;a href=&quot;#基于NgClass的主题切换&quot; class=&quot;headerlink&quot; title=&quot;基于NgClass的主题切换&quot;&gt;&lt;/a&gt;基于NgClass的主题切换&lt;/h1&gt;&lt;p&gt;Angular常见的主题方案是将组件设置Ng</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>GitLab持续集成与SonarQube代码质量管理</title>
    <link href="https://jasontsang.dev/gitlab-runner-with-sonarqube/"/>
    <id>https://jasontsang.dev/gitlab-runner-with-sonarqube/</id>
    <published>2019-05-05T00:40:37.000Z</published>
    <updated>2024-10-09T01:35:51.568Z</updated>
    
    <content type="html"><![CDATA[<h1 id="关于"><a href="#关于" class="headerlink" title="关于"></a>关于</h1><p>GitLab内置了持续集成能力，它可以将保存在共享仓库中的代码进行自动化编译、测试，并配合SonarQube进行自动化代码评审和代码质量管理。本文主要重点讲述GitLab与SonarQube的协作部署，并以Angular项目为例演示持续集成实践。</p><h1 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h1><p>为了简化部署流程，这里以docker环境作为基础。</p><ul><li><p>GitLab</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">//部署</span><br><span class="line">docker run --detach \</span><br><span class="line">  --hostname gitlab.jasontsang.dev \</span><br><span class="line">  --publish 443:443 --publish 80:80 --publish 22:22 \</span><br><span class="line">  --name gitlab \</span><br><span class="line">  --restart always \</span><br><span class="line">  --volume /srv/gitlab/config:/etc/gitlab \</span><br><span class="line">  --volume /srv/gitlab/logs:/var/log/gitlab \</span><br><span class="line">  --volume /srv/gitlab/data:/var/opt/gitlab \</span><br><span class="line">  gitlab/gitlab-ce:latest</span><br><span class="line"></span><br><span class="line">//进入容器</span><br><span class="line">docker exec -it gitlab /bin/bash</span><br><span class="line">//查看容器ip，用于GitLab Runner注册</span><br><span class="line">cat /etc/hosts</span><br></pre></td></tr></table></figure></li><li><p>GitLab Runner</p><ol><li><p>部署对应的docker容器</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">//部署</span><br><span class="line">docker run -d --name gitlab-runner --restart always \</span><br><span class="line">  -v /srv/gitlab-runner/config:/etc/gitlab-runner \</span><br><span class="line">  -v /var/run/docker.sock:/var/run/docker.sock \</span><br><span class="line">  gitlab/gitlab-runner:latest</span><br><span class="line"></span><br><span class="line">//进入容器</span><br><span class="line">docker exec -it gitlab-runner /bin/bash</span><br><span class="line">//安装node环境和相关工具</span><br><span class="line">//angular</span><br><span class="line">npm install -g @angular/cli</span><br><span class="line">npm install -g protractor</span><br><span class="line">//sonar-scanner</span><br><span class="line">npm install -g sonar-scanner</span><br></pre></td></tr></table></figure></li><li><p>进入<a href="http://127.0.0.1/username/ng-cari/settings/ci_cd">http://127.0.0.1/username/ng-cari/settings/ci_cd</a> 查看GitLab Runner的注册令牌Registration Token</p></li><li><p>注册GitLab Runner</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">docker run --rm -t -i -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register -n \</span><br><span class="line">  --url `GitLab对应的url` \</span><br><span class="line">  --registration-token `GitLab对应的GitLab Runner Registration Token` \</span><br><span class="line">  --executor shell \</span><br><span class="line">  --description &quot;Docker Runner&quot; \</span><br><span class="line">  --docker-image &quot;docker:stable&quot; \</span><br><span class="line">  --docker-privileged</span><br></pre></td></tr></table></figure></li></ol></li><li><p>SonarQube</p><p>本文写作时SonarQube的版本已经发布到7.7，但用于实现与GitLab进行交互的SonarQube插件目前仅支持7.5，所以这里以7.5做为演示。</p><ol><li><p>部署对应的docker容器</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">docker run --detach \</span><br><span class="line">  --publish 9000:9000 \</span><br><span class="line">  --name sonarqube \</span><br><span class="line">  --restart always \</span><br><span class="line">  sonarqube:7.5-community</span><br></pre></td></tr></table></figure></li><li><p>进入<a href="http://127.0.0.1:9000/admin/marketplace">http://127.0.0.1:9000/admin/marketplace</a> 并安装GitLab插件</p></li><li><p>进入<a href="http://127.0.0.1/profile/personal_access_tokens">http://127.0.0.1/profile/personal_access_tokens</a> 生成GitLab的Access Token访问令牌</p></li><li><p>进入<a href="http://127.0.0.1:9000/admin/settings?category=gitlab">http://127.0.0.1:9000/admin/settings?category=gitlab</a> 配置GitLab插件信息</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">GitLab url: http://127.0.0.1</span><br><span class="line">GitLab User Token: `步骤2中生成的GitLab Access Token`</span><br></pre></td></tr></table></figure></li><li><p>进入<a href="http://127.0.0.1:9000/admin/users">http://127.0.0.1:9000/admin/users</a> 在Administrator用户下建立SonarQube的Token访问令牌。</p></li></ol></li></ul><h1 id="持续集成"><a href="#持续集成" class="headerlink" title="持续集成"></a>持续集成</h1><ol><li><p>编写GitLab持续集成脚本，并上传项目代码。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br></pre></td><td class="code"><pre><span class="line">stages:</span><br><span class="line">  - prepare</span><br><span class="line">  - lint</span><br><span class="line">  - build</span><br><span class="line">  - test</span><br><span class="line">  - analyse</span><br><span class="line">  - quality</span><br><span class="line"></span><br><span class="line">#缓存node_modules</span><br><span class="line">cache:</span><br><span class="line">  paths:</span><br><span class="line">    - node_modules/</span><br><span class="line"></span><br><span class="line">#安装依赖</span><br><span class="line">prepare:ng:</span><br><span class="line">  stage: prepare</span><br><span class="line">  script:</span><br><span class="line">    - npm install</span><br><span class="line">  artifacts:</span><br><span class="line">    paths:</span><br><span class="line">      - node_modules/</span><br><span class="line"></span><br><span class="line">#feature分支代码检查</span><br><span class="line">lint:ng:</span><br><span class="line">  stage: lint</span><br><span class="line">  only:</span><br><span class="line">    - /^feature\/*/</span><br><span class="line">  script:</span><br><span class="line">    #lint代码检查如果失败则直接结束，并拒绝merge request</span><br><span class="line">    - ng lint</span><br><span class="line">    #将feature分支代码merge到master分支，并进行SonarQube代码检查</span><br><span class="line">    - git config user.email &quot;jasontsang.dev@gmail.com&quot;</span><br><span class="line">    - git config user.name &quot;Jason Tsang&quot;</span><br><span class="line">    - git checkout origin/master</span><br><span class="line">    - git merge $CI_COMMIT_SHA --no-commit --no-ff</span><br><span class="line">    - &gt;</span><br><span class="line">      sonar-scanner</span><br><span class="line">      -Dsonar.host.url=http://127.0.0.1:9000</span><br><span class="line">      #SonarQube Token</span><br><span class="line">      -Dsonar.login=a0d764c3a0b5856450c5a40d74d4c98e9fe57cd8</span><br><span class="line">      -Dsonar.analysis.mode=preview</span><br><span class="line">      -Dsonar.sources=src,e2e/src,projects/ng-cari/lottie/src</span><br><span class="line">      -Dsonar.gitlab.commit_sha=$CI_COMMIT_SHA</span><br><span class="line">      -Dsonar.gitlab.ref_name=$CI_COMMIT_REF_NAME</span><br><span class="line">      -Dsonar.gitlab.project_id=$CI_PROJECT_ID</span><br><span class="line">      -Dsonar.gitlab.json_mode=CODECLIMATE</span><br><span class="line">      -Dsonar.gitlab.failure_notification_mode=commit-status</span><br><span class="line">  artifacts:</span><br><span class="line">    expire_in: 1 day</span><br><span class="line">    paths:</span><br><span class="line">      - codeclimate.json</span><br><span class="line"></span><br><span class="line">#项目构建</span><br><span class="line">build:ng:</span><br><span class="line">  stage: build</span><br><span class="line">  script:</span><br><span class="line">    - ng build @ng-cari/lottie</span><br><span class="line">    - ng build</span><br><span class="line">  artifacts:</span><br><span class="line">    paths:</span><br><span class="line">      - dist/</span><br><span class="line"></span><br><span class="line">#测试</span><br><span class="line">test:ng:</span><br><span class="line">  stage: test</span><br><span class="line">  script:</span><br><span class="line">    - ng test --no-watch --no-progress --browsers=ChromeHeadlessCI --code-coverage</span><br><span class="line">  dependencies:</span><br><span class="line">    - build:ng</span><br><span class="line">  artifacts:</span><br><span class="line">    paths:</span><br><span class="line">      - coverage/</span><br><span class="line"></span><br><span class="line">#master分支代码检查需要上传测试覆盖，所以在测试之后执行</span><br><span class="line">analyse:master:</span><br><span class="line">  stage: analyse</span><br><span class="line">  only:</span><br><span class="line">    - master</span><br><span class="line">  script:</span><br><span class="line">    #使用lint生成外部报告并导入SonarQube</span><br><span class="line">    - ng lint ng-cari --format json --force &gt; report.json</span><br><span class="line">    - ng lint ng-cari-e2e --format json --force &gt; e2e/report.json</span><br><span class="line">    - ng lint @ng-cari/lottie --format json --force &gt; projects/ng-cari/lottie/report.json</span><br><span class="line">    - &gt;</span><br><span class="line">      sonar-scanner</span><br><span class="line">      -Dsonar.host.url=http://127.0.0.1:9000</span><br><span class="line">      -Dsonar.login=a0d764c3a0b5856450c5a40d74d4c98e9fe57cd8</span><br><span class="line">      -Dsonar.sources=src,e2e/src,projects/ng-cari/lottie/src</span><br><span class="line">      -Dsonar.typescript.tslint.reportPaths=report.json,e2e/report.json,projects/ng-cari/lottie/report.json</span><br><span class="line">      -Dsonar.typescript.lcov.reportPaths=coverage/ng-cari/lcov.info,coverage/ng-cari/lottie/lcov.info</span><br><span class="line">      -Dsonar.gitlab.commit_sha=$CI_COMMIT_SHA</span><br><span class="line">      -Dsonar.gitlab.ref_name=$CI_COMMIT_REF_NAME</span><br><span class="line">      -Dsonar.gitlab.project_id=$CI_PROJECT_ID</span><br><span class="line">      -Dsonar.gitlab.json_mode=CODECLIMATE</span><br><span class="line">      -Dsonar.gitlab.failure_notification_mode=commit-status</span><br><span class="line">  dependencies:</span><br><span class="line">    - test:ng</span><br><span class="line">  artifacts:</span><br><span class="line">    expire_in: 1 day</span><br><span class="line">    paths:</span><br><span class="line">      - codeclimate.json</span><br><span class="line"></span><br><span class="line">#上传codeclimate</span><br><span class="line">codequality:</span><br><span class="line">  stage: quality</span><br><span class="line">  variables:</span><br><span class="line">    GIT_STRATEGY: none</span><br><span class="line">  script:</span><br><span class="line">    - echo ok</span><br><span class="line">  artifacts:</span><br><span class="line">    paths:</span><br><span class="line">      - codeclimate.json</span><br></pre></td></tr></table></figure></li><li><p>查看当前commit的global comment，在最底部会有对应生成的代码质量报告。</p></li></ol><h1 id="代码评审和代码质量管理"><a href="#代码评审和代码质量管理" class="headerlink" title="代码评审和代码质量管理"></a>代码评审和代码质量管理</h1><ol><li><p>新建feature分支，并添加code smell（部分糟糕的代码）。</p></li><li><p>提交代码之后，根据脚本设定在持续集成中会首先进行ng lint，如果此时未通过则pipeline失败，开发者应该检查原因并修复代码问题。如果pipeline成功，则可以进行merge request。</p></li><li><p>项目维护人员在收到merge request之后，可以查看对应commit的pipeline运行情况以及global comment下的SonarQube代码质量报告，并根据情况选择是否同意merge request。</p></li><li><p>merge request被通过之后，会进行新一轮的持续集成，同样可以在对应commit的global comment下看到本次的SonarQube代码检查报告。</p></li><li><p>项目维护人员可以在SonarQube控制台下查看项目对应的完整质量报告信息，以及定义对应的Quality Gate，来评估项目是否可以发布。</p></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;关于&quot;&gt;&lt;a href=&quot;#关于&quot; class=&quot;headerlink&quot; title=&quot;关于&quot;&gt;&lt;/a&gt;关于&lt;/h1&gt;&lt;p&gt;GitLab内置了持续集成能力，它可以将保存在共享仓库中的代码进行自动化编译、测试，并配合SonarQube进行自动化代码评审和代码质量管理</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Angular Libraries构建与npm packages发布</title>
    <link href="https://jasontsang.dev/angular-libraries/"/>
    <id>https://jasontsang.dev/angular-libraries/</id>
    <published>2019-04-28T05:25:43.000Z</published>
    <updated>2024-10-09T01:35:51.568Z</updated>
    
    <content type="html"><![CDATA[<h1 id="关于"><a href="#关于" class="headerlink" title="关于"></a>关于</h1><p>按照官方的说法，Angular Library是一个不同于app的Angular工程，它需要在一个app中被引入使用。开发者可以以Angular Libraries的形式来为特定的场景提供通用的解决方案，从而可以在不同的app中得到重用。</p><h1 id="Angular-Libraries构建"><a href="#Angular-Libraries构建" class="headerlink" title="Angular Libraries构建"></a>Angular Libraries构建</h1><ol><li><p>Angular Libraries不能单独存在，首先需要创建一个app工程</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ng new ng-cari -p ng-cari</span><br></pre></td></tr></table></figure><p>这里的”-p”是指组件的前缀</p></li><li><p>进入新建的app工程根目录，并创建一个Angular Library工程</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ng g library @ng-cari/lottie -p ng-cari</span><br></pre></td></tr></table></figure><p>这里的”@ng-cari&#x2F;“是指npm包作用域，Angular CLI会自动修改angular.json，新增名为”@ng-cari&#x2F;lottie”的工程，并生成对应的Angular Library工程目录，工程目录位于projects&#x2F;ng-cari&#x2F;lottie</p></li><li><p>修改Angular Library代码，并添加依赖项以及其他对应信息</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    &quot;name&quot;: &quot;@ng-cari/lottie&quot;,</span><br><span class="line">    &quot;version&quot;: &quot;1.0.0&quot;,</span><br><span class="line">    &quot;author&quot;: &quot;Jason Tsang&quot;,</span><br><span class="line">    &quot;peerDependencies&quot;: &#123;</span><br><span class="line">        &quot;@angular/common&quot;: &quot;^7.2.0&quot;,</span><br><span class="line">        &quot;@angular/core&quot;: &quot;^7.2.0&quot;</span><br><span class="line">    &#125;,</span><br><span class="line">    &quot;dependencies&quot;: &#123;</span><br><span class="line">        &quot;lottie-web&quot;: &quot;^5.5.2&quot;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>在app工程根目录下添加Angular Library所需的依赖项</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -S lottie-web</span><br></pre></td></tr></table></figure></li><li><p>在app工程根目录下构建Angular Library工程</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ng build @ng-cari/lottie</span><br></pre></td></tr></table></figure></li><li><p>修改app工程代码，引入Angular Library工程，并使用对应的组件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">import &#123; LottieModule &#125; from &#x27;@ng-cari/lottie&#x27;;</span><br><span class="line"></span><br><span class="line">&lt;ng-cari-lottie [ngStyle]=&quot;&#123;&#x27;width.px&#x27;: &#x27;256&#x27;, &#x27;height.px&#x27;: &#x27;256&#x27;, &#x27;background&#x27;: &#x27;black&#x27;&#125;&quot;</span><br><span class="line">path=&quot;assets/lottie/progress.json&quot; progress=&quot;20&quot;&gt;&lt;/ng-cari-lottie&gt;</span><br></pre></td></tr></table></figure></li><li><p>运行app工程查看实际效果</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ng serve</span><br></pre></td></tr></table></figure></li></ol><h1 id="npm-packages发布"><a href="#npm-packages发布" class="headerlink" title="npm packages发布"></a>npm packages发布</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">//进入构建后的Angular Library工程目录，示例位于dist/ng-cari/lottie</span><br><span class="line">cd dist/ng-cari/lottie</span><br><span class="line"></span><br><span class="line">//如果使用其他源推荐使用nrm源管理工具</span><br><span class="line">npm install -g nrm</span><br><span class="line"></span><br><span class="line">//添加nexus源</span><br><span class="line">//组</span><br><span class="line">nrm add &lt;group&gt; http://127.0.0.1:8081/repository/npm-group/</span><br><span class="line">//私有</span><br><span class="line">nrm add &lt;hosted&gt; http://127.0.0.1:8081/repository/npm-hosted/</span><br><span class="line">//查看源</span><br><span class="line">nrm ls</span><br><span class="line">//使用私有</span><br><span class="line">nrm use &lt;hosted&gt;</span><br><span class="line"></span><br><span class="line">//登录npm账号</span><br><span class="line">npm login</span><br><span class="line"></span><br><span class="line">//发布，带包作用域的需要有创建私有包的权限或添加公开权限参数</span><br><span class="line">npm publish</span><br><span class="line">//由于示例带@ng-cari包作用域，所以这里使用公开权限参数</span><br><span class="line">npm publish --access public</span><br><span class="line"></span><br><span class="line">//使用组</span><br><span class="line">nrm use &lt;group&gt;</span><br><span class="line"></span><br><span class="line">//在其他工程中使用发布后的包</span><br><span class="line">npm install -S @ng-cari/lottie</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;关于&quot;&gt;&lt;a href=&quot;#关于&quot; class=&quot;headerlink&quot; title=&quot;关于&quot;&gt;&lt;/a&gt;关于&lt;/h1&gt;&lt;p&gt;按照官方的说法，Angular Library是一个不同于app的Angular工程，它需要在一个app中被引入使用。开发者可以以Angul</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>Electron学习笔记</title>
    <link href="https://jasontsang.dev/electron/"/>
    <id>https://jasontsang.dev/electron/</id>
    <published>2019-04-19T05:20:13.000Z</published>
    <updated>2024-10-09T01:35:51.568Z</updated>
    
    <content type="html"><![CDATA[<h1 id="关于"><a href="#关于" class="headerlink" title="关于"></a>关于</h1><p>按照官方的说法，Electron是由Github开发，通过HTML、CSS和JavaScript来构建跨平台桌面应用程序的一个开源库。因此可以基于Electron来创建原生的桌面应用，它可以具备操作窗口、菜单、通知、文件等原生能力。这也是本次学习使用Electron的主要目的。</p><h1 id="快速入门"><a href="#快速入门" class="headerlink" title="快速入门"></a>快速入门</h1><p>从Github上拉取Electron API 演示应用程序(<a href="https://github.com/electron/electron-api-demos">https://github.com/electron/electron-api-demos</a>) ，由于演示程序也是基于Electron构建，所以可以通过分析其源码来熟悉一个常规的Electron程序的整体结构以及掌握包括编译、打包、运行、生成安装包等构建过程。其次，演示程序提供了调用API的示例代码，通过分析示例代码、查看实时运行效果可以快速掌握API的使用。<br><img src="/images/electron-api-demos.png" style="width: 50%; height: 50%; margin-left: 0px;"></p><h1 id="项目构建"><a href="#项目构建" class="headerlink" title="项目构建"></a>项目构建</h1><p>使用官方推荐的electron forge来构建项目</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ npm install -g electron-forge</span><br><span class="line">$ electron-forge init electron-demo</span><br><span class="line">$ electron-forge start</span><br></pre></td></tr></table></figure><img src="/images/electron-forge-start.png" style="width: 50%; height: 50%; margin-left: 0px;">打包并生成安装程序<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ electron-forge make</span><br><span class="line">√ Checking your system</span><br><span class="line">√ Resolving Forge Config</span><br><span class="line">We need to package your application before we can make it</span><br><span class="line">√ Preparing to Package Application for arch: x64</span><br><span class="line">√ Compiling Application</span><br><span class="line">√ Preparing native dependencies</span><br><span class="line">√ Packaging Application</span><br><span class="line">Making for the following targets:</span><br><span class="line">√ Making for target: squirrel - On platform: win32 - For arch: x64</span><br></pre></td></tr></table></figure>![](/images/electron-forge-make-1.png)![](/images/electron-forge-make-2.png)<h1 id="Electron-API"><a href="#Electron-API" class="headerlink" title="Electron API"></a>Electron API</h1><p>常用的Electron API在Electron API 演示应用程序中均有示例代码，且可以实时查看运行效果，这里就不再赘述。以下主要记录一些API使用过程中遇到的问题以及注意事项。</p><ul><li>通知API不生效<br>在Github上的issue列表发现已有人提过此问题<img src="/images/electron-notification-issue.png">大致意思是AppUserModelId在不主动设置的情况下会被设置为某个默认值，导致通知的AppUserModelId和进程的AppUserModelId不匹配。解决方案如下：<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">//1.将运行时环境添加到开始菜单（windows10要求，否则无法接收到通知。）</span><br><span class="line">//node_modules\electron\dist\electron.exe</span><br><span class="line">//2.主动设置AppUserModelId</span><br><span class="line">app.setAppUserModelId(process.execPath)</span><br></pre></td></tr></table></figure></li><li>进程间通讯API<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">//主进程向渲染进程发送消息</span><br><span class="line">mainWindow.webContents.send(&#x27;&lt;event&gt;&#x27;, &lt;request&gt;);</span><br><span class="line"></span><br><span class="line">//渲染进程向主进程发送消息</span><br><span class="line">ipcRenderer.send(&#x27;&lt;event&gt;&#x27;, &lt;request&gt;);</span><br><span class="line"></span><br><span class="line">//主进程接收消息并返回</span><br><span class="line">ipcMain.on(&#x27;&lt;event&gt;&#x27;, (event, &lt;request&gt;) =&gt; &#123;</span><br><span class="line">    event.sender.send(&#x27;&lt;event&gt;&#x27;, &lt;response&gt;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">//渲染进程接收消息并返回</span><br><span class="line">ipcRenderer.on(&#x27;&lt;event&gt;&#x27;, (event, &lt;request&gt;) =&gt; &#123;</span><br><span class="line">    event.sender.send(&#x27;&lt;event&gt;&#x27;, &lt;response&gt;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">//渲染进程间通讯可以通过WindowID来指定发送和接收方</span><br><span class="line">const firstWindowID = BrowserWindow.getFocusedWindow().id;</span><br><span class="line">secondWindow.webContents.send(&#x27;&lt;event&gt;&#x27;, &lt;request&gt;, firstWindowID);</span><br><span class="line"></span><br><span class="line">ipcRenderer.on(&#x27;&lt;event&gt;&#x27;, (event, &lt;request&gt;, firstWindowID) =&gt; &#123;</span><br><span class="line">    const firstWindow = BrowserWindow.fromId(firstWindowID);</span><br><span class="line">    firstWindow.webContents.send(&#x27;&lt;event&gt;&#x27;, &lt;response&gt;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;关于&quot;&gt;&lt;a href=&quot;#关于&quot; class=&quot;headerlink&quot; title=&quot;关于&quot;&gt;&lt;/a&gt;关于&lt;/h1&gt;&lt;p&gt;按照官方的说法，Electron是由Github开发，通过HTML、CSS和JavaScript来构建跨平台桌面应用程序的一个开源库。因此可</summary>
      
    
    
    
    
  </entry>
  
</feed>
