<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title>Code - Category - ICHARM</title>
        <link>http://example.org/categories/code/</link>
        <description>Code - Category - ICHARM</description>
        <generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 31 Dec 2020 05:38:30 &#43;0000</lastBuildDate><atom:link href="http://example.org/categories/code/" rel="self" type="application/rss+xml" /><item>
    <title>一种基于时间戳的随机字符串生成方式</title>
    <link>http://example.org/random-string-base-timestamp/index.zh-cn.html</link>
    <pubDate>Thu, 31 Dec 2020 05:38:30 &#43;0000</pubDate>
    <author>Lake Mei</author>
    <guid>http://example.org/random-string-base-timestamp/index.zh-cn.html</guid>
    <description><![CDATA[想实现一个由时间戳+随机字符串组合方式生成重复概率很低的随机字符串，但是纯数字的时间戳是在太显眼，不想让人猜到里面包含了时间戳信息，怎么办，那就对0-9的数字进行编码吧。
最初想到的是使用base64来编码，但是有个问题，时间戳的前三位基本不变动，导致base64编码后的的时间戳还是具有一定的规律性，这不是我想要的。
思虑再三，决定自己来实现一个数字编码算法，将0-9进行随机编码，每个数字都对应着6种可能的字符来替代，比如0 ，可以由 y\p\z\3\O\G 来表示，至于选择这六个字符种的哪一种，则随机决定
首先定义编码字典，用数组来保存：
private val EncodeDict = Seq( &#39;y&#39;, &#39;i&#39;, &#39;o&#39;, &#39;q&#39;, &#39;w&#39;, &#39;e&#39;, &#39;r&#39;, &#39;t&#39;, &#39;y&#39;, &#39;u&#39;, &#39;p&#39;, &#39;a&#39;, &#39;s&#39;, &#39;d&#39;, &#39;f&#39;, &#39;g&#39;, &#39;C&#39;, &#39;T&#39;, &#39;F&#39;, &#39;X&#39;, &#39;z&#39;, &#39;x&#39;, &#39;c&#39;, &#39;v&#39;, &#39;b&#39;, &#39;n&#39;, &#39;m&#39;, &#39;0&#39;, &#39;1&#39;, &#39;2&#39;, &#39;3&#39;, &#39;U&#39;, &#34;H&#34;, &#39;V&#39;, &#39;Y&#39;, &#39;8&#39;, &#39;9&#39;, &#39;P&#39;, &#39;L&#39;, &#39;M&#39;, &#39;O&#39;, &#39;K&#39;, &#39;N&#39;, &#39;I&#39;, &#39;J&#39;, &#39;B&#39;, &#39;4&#39;, &#39;5&#39;, &#39;6&#39;, &#39;7&#39;, &#39;G&#39;, &#39;h&#39;, &#39;j&#39;, &#39;k&#39;, &#39;l&#39;, &#39;R&#39;, &#39;D&#39;, &#39;Z&#39;, &#39;E&#39;, &#39;S&#39;) 共有60个字符，分成6行10列， 每一列表示一个数字，0 由 y\p\z\3\O\G 来表示，1由 i\a\x\U\K\h 来表示， 以此类推。]]></description>
</item>
<item>
    <title>FFMpeg抽帧截图（抽取封面图）</title>
    <link>http://example.org/ffmpeg%E6%8A%BD%E5%B8%A7%E6%88%AA%E5%9B%BE%EF%BC%88%E6%8A%BD%E5%8F%96%E5%B0%81%E9%9D%A2%E5%9B%BE%EF%BC%89.html</link>
    <pubDate>Thu, 04 Jun 2020 09:49:29 &#43;0000</pubDate>
    <author>Elias</author>
    <guid>http://example.org/ffmpeg%E6%8A%BD%E5%B8%A7%E6%88%AA%E5%9B%BE%EF%BC%88%E6%8A%BD%E5%8F%96%E5%B0%81%E9%9D%A2%E5%9B%BE%EF%BC%89.html</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/feature-covers/ffmpeg.jpg" referrerpolicy="no-referrer">
            </div>ffmpeg是视频处理领域最常用的开源软件，用来对视频文件进行格式转换，抽帧截图、抽取音频、加水印、剪切、合并 等等操作，非常的强大。
这里只介绍下使用ffmpeg来抽取视频文件的某一帧， 博主碰到的需求是抽取视频某一帧来当作封面图，经过研究发现有两种方式可以实现这个需求。
按照固定时间点 按照固定时间点来截图，比较好容易理解，比如每次抽取视频第1s的画面，ffmpeg 命令如下：
./ffmpeg -ss 00:00:01 -t 00:00:02 -i ~/video_7783_aed4.mp4 -frames:v 1 -pix_fmt yuvj422p -f image2 /tmp/video_7783_aed4.mp4.jpg -y&lt;/code&gt;&lt;/pre&gt; 视频是由一帧一帧的图片组成，但并不是每一帧画面都是完整存在的，所以按照固定时间点抽取的方式需要ffmpeg根据前后帧数据和视频压缩算法 来计算出某时间点的画面，会比较占用CPU。
抽取关键帧 视频关键帧（Video Keyframes）是用于视频压缩和视频编解码的帧，视频关键帧是包含了完整信息的帧，其他的非关键帧将会使用与关键帧的差值进行压缩。视频帧具体可以分为IPB帧三种：
I帧表示关键帧，是最完整的帧画面，一般视频封面都选择I帧； P帧单预测帧，利用之前的I帧或P帧，采用运动预测的方式进行帧间预测编码； B帧双向预测帧，利用双向帧进行预测编码； 一般情况下关键帧`I帧`是信息最多的帧，也是用途最多的帧。在视频检索和视频分类任务中一般都借助`I帧`来完成，在一个时长60s的视频中，可以抽取得到16个I帧、84个P帧和184个B，I帧数量少包含的信息却是最多的。
抽取关键帧作为视频的封面图是比较合适，且关键帧的画面比较完整，需要的CPU计算较少， ffmpeg命令如下：
./ffmpeg -i ～/video_7783_aed4.mp4 -vf &#34;select=eq(pict_type\,I)&#34; -frames:v 1 -pix_fmt yuvj422p -vsync vfr -qscale:v 2 -f image2 /tmp/video_7783_aed4.mp4.jpg -y ]]></description>
</item>
<item>
    <title>Use Akka-Quartz-Scheduler for cron schedule in Play Framework 2.7</title>
    <link>http://example.org/use-akka-quartz-scheduler-for-cron-schedule-in-play-framework-2-7.html</link>
    <pubDate>Mon, 06 May 2019 05:14:56 &#43;0000</pubDate>
    <author>Elias</author>
    <guid>http://example.org/use-akka-quartz-scheduler-for-cron-schedule-in-play-framework-2-7.html</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/feature-covers/play.png" referrerpolicy="no-referrer">
            </div>The Akka-Quartz-Scheduler is a commended project by play official for implementing cron task scheduling , refer: https://www.playframework.com/documentation/2.7.x/ModuleDirectory#Akka-Quartz-Scheduler
But Akka-Quartz-Scheduler documentation does not cover the play framework, so there is a article to show how to use Akka-Quartz-Scheduler to achieve Cron-like scheduling when play app startup.
You can find this demo project in github.
Simple Steps Step 1. Add Dependencies in build.sbt
// For Akka 2.5.x and Scala 2.11.x, 2.12.x libraryDependencies += &#34;com.]]></description>
</item>
<item>
    <title>利用BeanUtils.copyProperties实现Java Pojo的单元测试覆盖</title>
    <link>http://example.org/%E5%88%A9%E7%94%A8beanutils-copyproperties%E5%AE%9E%E7%8E%B0java-pojo%E7%9A%84%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E8%A6%86%E7%9B%96.html</link>
    <pubDate>Wed, 06 Mar 2019 11:45:32 &#43;0000</pubDate>
    <author>Elias</author>
    <guid>http://example.org/%E5%88%A9%E7%94%A8beanutils-copyproperties%E5%AE%9E%E7%8E%B0java-pojo%E7%9A%84%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E8%A6%86%E7%9B%96.html</guid>
    <description><![CDATA[<div class="featured-image">
                <img src="/feature-covers/java.png" referrerpolicy="no-referrer">
            </div>Java Pojo及普通的javaBean类，里面都是private 元素和Getter、Setter、toString等方法，我更喜欢把它称之为数据模型类（Model）。
在Java项目一般都有大量的Pojo类文件，如果对他们一个一个的进行单元测试的覆盖就很枯燥，也没有意义。下面就使用Spring 的BeanUtils.copyProperties方法来实现自动的测试覆盖。
基本思路是这样子的，利用BeanUtils.copyProperties对两个Pojo类实例的属性copy，这个过程会分别调用两个实例的Getter和Setter方法，从而实现Getter和Setter方法的覆盖，加上使用反射获取所有的类对象。
package com.icharm.pojo.test; import org.springframework.beans.BeanUtils; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.testng.annotations.Test; import java.util.ArrayList; import java.util.List; public class BeanDmoTest { String CLASSPATH_ALL_URL_PREFIX = &#34;classpath:&#34;; @Test public void beanTest() throws Exception { List list = new ArrayList(); list.add(&#34;com.xxx.a.dto&#34;); list.add(&#34;com.xxx.b.dto&#34;); list.add(&#34;com.xxx.c.dto&#34;); for (String path : list) { // 包名转路径 path = path.replaceAll(&#34;\\.&#34;, &#34;\\/&#34;); String packageSearchPath = CLASSPATH_ALL_URL_PREFIX + path + &#34;/**/*.class&#34;; // 获取所有的Pojo类 ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resourcePatternResolver.]]></description>
</item>
<item>
    <title>Thymeleaf布局踩坑</title>
    <link>http://example.org/thymeleaf%E5%B8%83%E5%B1%80%E8%B8%A9%E5%9D%91.html</link>
    <pubDate>Wed, 28 Nov 2018 12:57:23 &#43;0000</pubDate>
    <author>Elias</author>
    <guid>http://example.org/thymeleaf%E5%B8%83%E5%B1%80%E8%B8%A9%E5%9D%91.html</guid>
    <description><![CDATA[Thymeleaf模板引擎第一次使用，之前用过的都是freemarker，结合Spring boot记录一下初次使用时遇到的一些小坑，主要是路径和包名的一些问题。下面将记录使用thymeleaf的layout布局方式展现web内容的整个过程。
Build with Maven Thymeleaf有两种布局的方式，第一种是使用 th:include 和 th:replace这种的形式，第二种是使用layout:decorate这种。
本文使用第二种，需要引入thymeleaf-layout-dialect这个包，否则layout:decorate这种布局方式不会生效（踩到的第一个坑）
&lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt; nz.net.ultraq.thymeleaf&lt;/groupId&gt; &lt;artifactId&gt;thymeleaf-layout-dialect&lt;/artifactId&gt; &lt;/dependency&gt; ViewModel 这里对html模板进行了一个封装，使其可以更加灵活方便地使用，就是一个POJO类，对Template中变动的内容进行定义，在提供一个返回ModelAndView对象的方法。
package me.icharm.orange.ViewModel; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.beanutils.BeanUtils; import org.springframework.web.servlet.ModelAndView; import java.util.Map; /** * @author elias * @email icharm.me@outlook.com * @date 2018/11/21 17:40 */ @Data @Slf4j public class WeuiResultPage { /** * 视图模板路径 */ public String path = &#34;weui/result_fragment&#34;; /** * 图标 */ private String icon; /** * 标题 */ private String title; /** * 内容 */ private String content; /** * 绿色按钮名称 */ private String btnPrimary; /** * 白色按钮名称 */ private String btnDefault; /** * 绿色按钮事件 */ private String btnPrimaryAction; /** * 白色按钮事件 */ private String btnDefaultAction; /** * This object convert to ModelAndView object.]]></description>
</item>
<item>
    <title>IDEA根据数据库表结构生成Model类（POJO）</title>
    <link>http://example.org/idea%E6%A0%B9%E6%8D%AE%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%E7%BB%93%E6%9E%84%E7%94%9F%E6%88%90model%E7%B1%BB%EF%BC%88pojo%EF%BC%89.html</link>
    <pubDate>Fri, 14 Sep 2018 02:05:11 &#43;0000</pubDate>
    <author>Elias Mei</author>
    <guid>http://example.org/idea%E6%A0%B9%E6%8D%AE%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%E7%BB%93%E6%9E%84%E7%94%9F%E6%88%90model%E7%B1%BB%EF%BC%88pojo%EF%BC%89.html</guid>
    <description><![CDATA[idea自动的脚本可以将一个数据的表结构自动生成模型类，变量名采用驼峰命名方式。下面是一个简单的系统参数表创建的sql：
create table system_parameter ( ID bigint auto_increment comment &#39;主键&#39; primary key, SKEY varchar(32) not null comment &#39;键名&#39;, SVALUE varchar(256) null comment &#39;键值&#39;, REMARK varchar(256) null comment &#39;备注&#39;, OPERATER varchar(20) null comment &#39;操作人&#39;, CREATE_TIME datetime null comment &#39;创建时间&#39;, UPDATE_TIME datetime null comment &#39;更新时间&#39;, constraint UK_SYSTEM_PARAMETER unique (SYS_KEY) ) comment &#39;系统参数表&#39; charset = utf8; 自动生成该表对应的dmo类，操作如下：
1.表名上右击 - Scripted Extensions - Generate POJOs.groovy
1 然后选择保存的位置。 改进的Script Idea自带的script已经很强大了，但是如果想把表中注释信息，自动注释到dmo类上面，就需要使用下面的script。
import com.intellij.database.model.DasTable import com.intellij.database.model.ObjectKind import com.]]></description>
</item>
<item>
    <title>Spring boot redis 使用踩坑</title>
    <link>http://example.org/spring-boot-redis-%E4%BD%BF%E7%94%A8%E8%B8%A9%E5%9D%91.html</link>
    <pubDate>Fri, 14 Sep 2018 01:36:34 &#43;0000</pubDate>
    <author>Elias Mei</author>
    <guid>http://example.org/spring-boot-redis-%E4%BD%BF%E7%94%A8%E8%B8%A9%E5%9D%91.html</guid>
    <description><![CDATA[连接失败 windows下开发spring boot项目，需要使用redis服务代替session工作，所以直接使用homestead的虚拟机来提供数据库、redis服务等，免去自己配置各种服务的麻烦，redis服务本来使用laravel连接的时候一切正常，但是换到使用spring-boot-redis连接就提示连接被主机中的其他软件关闭，然后一直在reconnect，错误如下：
2018-08-30 15:35:05.969 ERROR 8892 --- [0.0.1-80-exec-1] m.i.o.C.Common.CommonController : org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is io.lettuce.core.RedisException: java.io.IOException: 您的主机中的软件中止了一个已建立的连接。 2018-08-30 15:35:06.038 INFO 8892 --- [ioEventLoop-4-3] i.l.core.protocol.ReconnectionHandler : Reconnected to 127.0.0.1:60030 2018-08-30 15:35:06.138 INFO 8892 --- [ioEventLoop-4-4] i.l.core.protocol.ReconnectionHandler : Reconnected to 127.0.0.1:60030 2018-08-30 15:35:06.241 INFO 8892 --- [ioEventLoop-4-1] i.l.core.protocol.ReconnectionHandler : Reconnected to 127.0.0.1:60030 2018-08-30 15:35:06.338 INFO 8892 --- [ioEventLoop-4-2] i.l.core.protocol.ReconnectionHandler : Reconnected to 127.0.0.1:60030 2018-08-30 15:35:06.439 INFO 8892 --- [ioEventLoop-4-3] i.]]></description>
</item>
<item>
    <title>ESP8266使用AT指令连接服务器（回传判断思路）</title>
    <link>http://example.org/esp8266%E4%BD%BF%E7%94%A8at%E6%8C%87%E4%BB%A4%E8%BF%9E%E6%8E%A5%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E5%9B%9E%E4%BC%A0%E5%88%A4%E6%96%AD%E6%80%9D%E8%B7%AF%EF%BC%89.html</link>
    <pubDate>Wed, 30 Nov 2016 11:31:59 &#43;0000</pubDate>
    <author>Elias Mei</author>
    <guid>http://example.org/esp8266%E4%BD%BF%E7%94%A8at%E6%8C%87%E4%BB%A4%E8%BF%9E%E6%8E%A5%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88%E5%9B%9E%E4%BC%A0%E5%88%A4%E6%96%AD%E6%80%9D%E8%B7%AF%EF%BC%89.html</guid>
    <description><![CDATA[最近在做一款单片机系统，使用的是AVR128单片机作为上位机，其中使用ESP8266接入互联网，大体的想法是，上位机通过串口向esp8266发送AT指令，来达到请求服务器接口的目的，服务器上的接口是使用PHP写的简单的HTTP接口。
最初的方法 esp8266的AT指令返回的信息非常的不规范，没有统一的格式，所以对回传的判断是有一定的困难的。刚开始使用的是最简单的方法通过充足的延时和指令的重复发送来确保每一个AT指令的正确执行。
上位机在初始化的时候会重启esp8266，这时候延时5秒的时间（用来esp8266的重启和自动连接好已连接过的热点） 发送TCP连接指令（AT+CIPSTART=&ldquo;TCP&rdquo;,&ldquo;www.icharm.me&rdquo;,80),这时候延时2秒（可以缩短为一秒，视服务器情况） 发送进入透传模式指令（AT+CIPMODE=1）,延时500ms 发送进入透传发送模式指令（AT+CIPSEND）,延时500ms 然后发送请求接口的数据（GET http://www.icharm.me/index.php?ac=100002）延时1s等待服务器的返回 如果上面的每一条指令都能正确的执行的话，肯定是可以触发服务器的接口的。但是经过测试发现，这种方法并不靠谱，经常会出现问题。
有关ESP8266怎么连接服务器的可以参考：
检查回传 因为上面的方法稳定性太差，所以哀差闷开始分析esp8266回传信息，试图找到一种通用的规则来判断AT指令的成功执行。
哀差闷的设想是保证每次开机后都顺利的进入透传发送模式，所以怎么保证进入了透传发送的模式，只有检查每一条AT指令的回传。确保每一条指令的成功执行。
首先需要知道的是：每发送一个AT指令，ESP8266会返回一下你发送的指令，接着紧接回车换行（0x0D 0x0A）,再返回指令执行的信息，再紧接回车换行（0x0D 0x0A），最后返回指令执行的情况（OK或者ERROR），最后再紧接一个回车换行。
接下来分析下AT+CIPSTART=&ldquo;TCP&rdquo;,&ldquo;www.icharm.me&rdquo;,80 这条AT指令的回传信息。发送这条指令的时候esp8266有四种回传情况：（/n/r 代表回车换行）
未连接wifi，这时候会返回：no ip /n/r ERROR /n/r 等信息 已连接wifi，但没有网络访问权限 ，这时候会返回 CONNECT CLOSE /n/r ERROR /n/r 已经和服务器建立连接，这时候会返回 ALREADY CONNECTED /n/r ERROR /n/r 成功和服务器建立连接， 返回OK 再来分析下AT+CIPMODE=1 指令的回传情况，这个比较简单，成功返回OK， 失败返回ERROR，重复设置也会返回OK，所以这条指令可以通过判断是否为OK或ERROR。
指令AT+CIPSEND 的返回信息比较简单，成功返回 &gt; 失败返回ERROR。
从上面可以知道，判断返回是否为OK或者ERROR并不能达到预期的目标，而且在单片机程序中，串口接受缓冲数组长度有限，会出现覆盖掉前面的一部分信息的情况，甚至出现ERROR这单词的前一部分在数组的末端，后一部分在数组的前端的情况。
所以哀差闷想了一个办法，先从串口中断接收函数入手，弄两个接收缓冲数组A,B，首先对接收到的字符进行判断如果为0x0d（即回车）时，则舍弃，同时从A数组中取出0x0d前面的一个字符存入B，如果为0x0a则直接放弃， 否则将存入数组A。
这样数组B中存放即为每一个回车的前一个字符。后面将通过判断数组B中的字符来判断，从面上面的情况中，可以总结出成功的情况有三种：
数组B最后一个字符为K （即OK） 数组B最后一个字符为R （即ERROR），但数组B倒数第二个字符为D，这种情况代表ALREADY CONNECTED /n/r ERROR 数组B最后一个字符为 &gt; 除了上面的三种情况，其他的都视为失败。
代码分析 可能经过上面的说明，还是不太明白，可以参考一下哀差闷的代码。但因为单片机的不同，代码会不同，请参考着看：
中断接收函数：
//*********************************************************************** // USART1中断接收字符串 //*********************************************************************** #pragma interrupt_handler uart1_rx_isr:iv_USART1_RXC void uart1_rx_isr(void) { uchar temp = UDR1; //从数据缓冲器中接收数据 放入temp中 if(temp == 0x0d ){ //将每一个回车符前面的一个字符串记录下来，用作判断的标志 if(RxBufWr_wifi == 0){ //RxCharBuf_wifi即为数组A, wifi_flag即为数组B， RxBufWr_wifi为数组A的写入指针, flagWr为数组B的写入指针 wifi_flag[flagWr] = RxCharBuf_wifi[15]; }else{ wifi_flag[flagWr] = RxCharBuf_wifi[RxBufWr_wifi-1]; } flagWr++; flagWr &amp;= 0x0f; //数组长度为16,当写入指针为达到16时 自动归零 if(flagWr !]]></description>
</item>
<item>
    <title>git pull 冲突解决</title>
    <link>http://example.org/git-pull-%E5%86%B2%E7%AA%81%E8%A7%A3%E5%86%B3.html</link>
    <pubDate>Tue, 13 Sep 2016 10:11:21 &#43;0000</pubDate>
    <author>Elias Mei</author>
    <guid>http://example.org/git-pull-%E5%86%B2%E7%AA%81%E8%A7%A3%E5%86%B3.html</guid>
    <description><![CDATA[git菜鸟应该会经常遇到的问题， 就像博主这样。服务器上pull更新时遇到冲突。主要是环境配置文件冲突，由于从SVN转到git，所以保留了SVN那种部署代码的方式，git有更好的部署方法flow，可惜还不会使用。配置文件分为本地和线上。有时候为了方便直接上服务器上修改配置文件，然后在本地完成一个新功能的时候，push到仓库里。再在服务器上拉取更新。就会爆冲突。这时候，索引的版本号会变为最新。但是更新的代码并没有实际合并到当前分支，再次尝试拉取的时候会提示已是最新版本。类似如下：
[root@smsms]$ git pull origin master:master remote: Counting objects: 680, done. remote: Compressing objects: 100% (517/517), done. remote: Total 680 (delta 166), reused 664 (delta 156) Receiving objects: 100% (680/680), 11.17 MiB | 22.13 MiB/s, done. Resolving deltas: 100% (166/166), completed with 7 local objects. From https://gitlab.oneit.com/mei/smsms d1cfc68..9e7ba97 master -&amp;gt; master Warning: fetch updated the current branch head. Warning: fast-forwarding your working tree from Warning: commit d1cfc689545cdf70a9e29254d4473fcb7fe66efc. error: Entry &amp;#039;inc/global.]]></description>
</item>
</channel>
</rss>
