Spock实践

Spock是什么?

Spock是一款国外优秀的测试框架,基于BDD(行为驱动开发)思想实现,功能非常强大。Spock结合Groovy动态语言的特点,提供了各种标签,并采用简单、通用、结构化的描述语言,让编写测试代码更加简洁、高效。官方的介绍如下:

img

What is it? Spock is a testing and specification framework for Java and Groovy applications. What makes it stand out from the crowd is its beautiful and highly expressive specification language. Thanks to its JUnit runner, Spock is compatible with most IDEs, build tools, and continuous integration servers. Spock is inspired from JUnit, RSpec, jMock, Mockito, Groovy, Scala, Vulcans, and other fascinating life forms.

Spock是一个Java和Groovy`应用的测试和规范框架。之所以能够在众多测试框架中脱颖而出,是因为它优美而富有表现力的规范语言。Spock的灵感来自JUnit、RSpec、jMock、Mockito、Groovy、Scala、Vulcans。

简单来讲,Spock主要特点如下:

  • 让测试代码更规范,内置多种标签来规范单元测试代码的语义,测试代码结构清晰,更具可读性,降低后期维护难度。
  • 提供多种标签,比如:givenwhenthenexpectwherewiththrown……帮助我们应对复杂的测试场景。
  • 使用Groovy这种动态语言来编写测试代码,可以让我们编写的测试代码更简洁,适合敏捷开发,提高编写单元测试代码的效率。
  • 遵从BDD(行为驱动开发)模式,有助于提升代码的质量。
  • IDE兼容性好,自带Mock功能。

笔者以前实践过PowerMock、Mockito,比原来的Junit好多了,但是现在都是使用的SpringBoot开发项目,大部分的类都是Spring的Bean,使用PowerMock对类Mock并不是很好用,测试起来也算是很麻烦了,那么Spock真的好用吗?

笔者从美团的技术博客上了解到Spock的实际使用,因此在团队中,我也实践了一把。基本的使用方法,美团的技术文章已经讲的很明白了,那我就讲点不一样的。

基于Gradle的项目配置

基于kotlin的Gradle配置build.gradle.kts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
plugins {
//引入gradle的Groovy插件
groovy
//使用jacoco生成测试报告
jacoco
}
val spockVersion = "2.0-groovy-2.5"
project {
apply(plugin = "groovy")
apply(plugin = "jacoco")

dependencies {

testImplementation("org.codehaus.groovy:groovy:2.5.9")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("junit:junit")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}

testImplementation("org.spockframework:spock-core:$spockVersion")
}
//配置测试源代码目录加上groovy的
sourceSets {

test {
java {srcDirs("src/test/java", "src/test/groovy")}
resources { srcDir("src/test/resources") }
}
}
//配置jacoco
tasks.test {
finalizedBy(tasks.jacocoTestReport)
}
tasks.jacocoTestReport {
dependsOn(tasks.test)
}
tasks.withType<Test> {
useJUnitPlatform()
}
}

Mock模拟

我们的项目现在使用的是Kotlin,Kotlin的类默认是final的,Mock时需要修改类为open,比如

1
2
3
open class A {

}

但是如果标记了Spring的Bean注解(@Service@Repository@Component)等,其实是默认已经修改过了,不需要额外去标记。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class FinLogisticsDeductionSpec extends Specification {
def finLogisticsAccountService = Mock(FinLogisticsAccountService.class)
def finLogisticsDeduction = new FinLogisticsDeductionAdvanceImpl(finLogisticsAccountService: finLogisticsAccountService)


def "测试 消费"() {
def req = new FinLogisticsDeductionExecBO(amount: new BigDecimal(100))
given: "设置消费现金100"
def cash = new BigDecimal(100)

and: "扣费服务返回扣费现金100"
finLogisticsAccountService.deduction(req, LogisticsAccountType.ADVANCE) >> cash

when: "扣费"
def result = finLogisticsDeduction.exec(req)

then: "期望结果"
with(result) {
it == cash
}
}
}

多分支条件测试

项目中经常会遇到同一个方法里面有多个if-else分支的情况,通过Spock的where标签可以非常容易的实现这个。方法名中的#amount,是直接在字符串模板中显示变量的值,可以更直观的显示在测试结果中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Unroll
def "扣费:#amount 当用户现金是:#account.availableCashAmount,红包是:#account.availableAwardAmount, 扣费后现金余额:#cash, 红包余额:#award, 返回剩余金额:#expectResult "() {
given:
def bo = new FinLogisticsDeductionExecBO(amount: amount, fromCompanyId: 1, comment: "订单扣费", deductedAfterRecharge: deductedAfterRecharge, billId: 1, billNo: "1234546")
and:
finLogisticsAccountItemRepository.selectUnusedList(account.id, false) >> accountItems
securityUtils.currentUser() >> currentUser
finLogisticsAccountItemRepository.updateById(_ as FinLogisticsAccountItemEntity) >> true

when: "扣现金"
def result = tester.loopDeduction(account, bo)

then:
with(result) {
expectResult == it
accountItems[0].availableCashBalance == cash
accountItems[0].availableAwardBalance == award

}

where: "验证分支"
amount | currentUser | account | accountItems | deductedAfterRecharge || cash | award | expectResult
10000 | currentUser() | getAccount(10000, 0) | getAccountItems(10000, 0) | true | 0 | 0 | 0
10000 | currentUser() | getAccount(0, 10000) | getAccountItems(0, 10000) | true | 0 | 0 | 0
10000 | currentUser() | getAccount(5000, 5000) | getAccountItems(5000, 5000) | true | 0 | 0 | 0
10000 | currentUser() | getAccount(4000, 5000) | getAccountItems(4000, 5000) | true | 0 | 0 | 1000
10000 | currentUser() | getAccount(6000, 5000) | getAccountItems(6000, 5000) | true | 0 | 1000 | 0
10000 | currentUser() | getAccount(10000, 0) | getAccountItems(10000, 0) | false | 0 | 0 | 0
10000 | currentUser() | getAccount(0, 10000) | getAccountItems(0, 10000) | false | 0 | 0 | 0
10000 | currentUser() | getAccount(5000, 5000) | getAccountItems(5000, 5000) | false | 0 | 0 | 0
10000 | new AuditData() | getAccount(4000, 5000) | getAccountItems(4000, 5000) | false | 0 | 0 | 1000
10000 | new AuditData() | getAccount(6000, 5000) | getAccountItems(6000, 5000) | false | 0 | 1000 | 0
0 | null | getAccount(6000, 5000) | getAccountItems(6000, 5000) | true | 6000 | 5000 | 0
0 | null | getAccount(6000, 5000) | getAccountItems(6000, 5000) | false | 6000 | 5000 | 0
100 | null | getAccount(0, 0) | getAccountItems(0, 0) | false | 0 | 0 | 100
}

image-20220211215607514

异常测试

对于方法逻辑中会抛出异常的逻辑,可以通过thrown(异常变量)方法来获取异常错误,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Unroll
def "测试退款现金不足 #expectedMessage"() {
given:
def bo = new FinLogisticsRefundExecBO(cashAmount: cashAmount, awardAmount: awardAmount, fromCompanyId: 1, comment: "订单扣费", billId: 1, billNo: "1234546")
def account = new FinLogisticsAccountEntity(id: 1)
and:
finLogisticsAccountItemRepository.selectUnusedList(account.id, false) >> accountItems
securityUtils.currentUser() >> currentUser

when:
tester.loopRefund(account, bo)
then:
def exception = thrown(expectedException)
exception.message == expectedMessage

where: "验证分支"
cashAmount | awardAmount | currentUser | accountItems || expectedException | expectedMessage
20000 | 0 | currentUser() | getAccountItems(10000, 0) || BaseException | "退款出错,现金金额不能大于账户现金余额"
20000 | 0 | currentUser() | getAccountItems(0, 0) || BaseException | "退款出错,现金金额不能大于账户现金余额"
0 | 20000 | currentUser() | getAccountItems(0, 10000) || BaseException | "退款出错,红包金额不能大于账户红包余额"
10000 | 11000 | currentUser() | getAccountItems(10000, 10000) || BaseException | "退款出错,红包金额不能大于账户红包余额"
0 | 100 | currentUser() | getAccountItems(0, 0) || BaseException | "退款出错,红包金额不能大于账户红包余额"

}

uni-app 包装H5项目集成微信登录支付和分享

引用UNI-APP的JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script type="text/javascript">
var userAgent = navigator.userAgent;
if (userAgent.indexOf('AlipayClient') > -1) {
// 支付宝小程序的 JS-SDK 防止 404 需要动态加载,如果不需要兼容支付宝小程序,则无需引用此 JS 文件。
document.writeln('<script src="https://appx/web-view.min.js"' + '>' + '<' + '/' + 'script>');
} else if (/QQ/i.test(userAgent) && /miniProgram/i.test(userAgent)) {
// QQ 小程序
document.write('<script type="text/javascript" src="https://qqq.gtimg.cn/miniprogram/webview_jssdk/qqjssdk-1.0.0.js"><\/script>');
} else if (/miniProgram/i.test(userAgent) && /micromessenger/i.test(userAgent)) {
// 微信小程序 JS-SDK 如果不需要兼容微信小程序,则无需引用此 JS 文件。
document.write('<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js"><\/script>');
} else if (/toutiaomicroapp/i.test(userAgent)) {
// 头条小程序 JS-SDK 如果不需要兼容头条小程序,则无需引用此 JS 文件。
document.write('<script type="text/javascript" src="https://s3.pstatp.com/toutiao/tmajssdk/jssdk-1.0.1.js"><\/script>');
} else if (/swan/i.test(userAgent)) {
// 百度小程序 JS-SDK 如果不需要兼容百度小程序,则无需引用此 JS 文件。
document.write('<script type="text/javascript" src="https://b.bdstatic.com/searchbox/icms/searchbox/js/swan-2.0.18.js"><\/script>');
} else if (/quickapp/i.test(userAgent)) {


// quickapp
document.write('<script type="text/javascript" src="https://quickapp/jssdk.webview.min.js"><\/script>');
}
if (!/toutiaomicroapp/i.test(userAgent)) {


document.querySelector('.post-message-section').style.visibility = 'visible';
}
</script>
<!-- uni 的 SDK -->
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>

登录,接收回调参数

userInof 是登录后json序列化编码后的字符串

1
http://m.zhcn.qfdenge.com/uc/login?userInfo=${userInfo}

支付

使用uni.requestPayment,参考 https://uniapp.dcloud.io/api/plugins/payment?id=requestpayment

其中的orderInfo的格式如下

1
2
3
4
5
6
7
8
9
{
"appid": "wx0411fa6a39d61297”,
"noncestr": "5JigiIJicbq8hQI2”,
"package": "Sign=WXPay”,
"partnerid": "1230636401”,
"prepayid": "wx21204902147233e222e12d451613768000”,
"timestamp": 1571662142,
"sign": “0E5C9B9B1C8D7497A234CCC3C721AB1F"
}

HTML页面需要传递过来的是

1
2
3
4
5
6
7
8
9
10
{
"appId": "wxf09c6920dc0f2230",
"partnerId": "1429576202",
"prepayId": "wx111943147461245fa3922a787fc7260000",
"packageValue": "Sign=WXPay",
"noncestr": "c71PJL2Q9AbuPd7gskqWORr3FoNYDSRA",
"timestamp": "1599824594",
"sign": "86F8C4EC4E8662CBD2DB3C0EC5D82557",
"orderNo": "21812009111551972"
}
1
2
3
4
5
6
7
8
9
10
11
12
if (!/toutiaomicroapp/i.test(userAgent)) {
var orderInfo = encodeURIComponent(JSON.stringify(result.data));
uni.navigateTo({
url: "/pages/pay/pay?orderInfo="+orderInfo
})
//弹出待查询界面
$.pay("", {
buttonHandler: function (layer) {
submitCheckOrder(orderId, layer);
}
});
}

分享

添加按钮点击事件,向uni传递消息数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (!/toutiaomicroapp/i.test(userAgent)) {


document.addEventListener('UniAppJSBridgeReady', function() {
document.getElementById("btn_share").addEventListener('click', function(evt) {
var shareUrl = shareData.url;
if (-1 == shareUrl.indexOf('back=share')){
shareUrl += (-1 == shareUrl.indexOf('?') ? '?' : '&') + 'back=share';
}
uni.postMessage({
data: {
title: shareData.title,
desc: shareData.desc,
link: shareUrl,
imgUrl: shareData.icon,


}
})
})
})
}

如此,就完成了UNI-APP集成微信登录\支付\分享的功能

SpringCloud+Gateway+nacos+sentinel入门

nacos的安装

从官方网站(https://github.com/alibaba/nacos/releases)下载稳定版1.3.2

以单机模式启动nacos

1
bin/nacos -m standalone

创建2个工程

spring-nacos-provider, spring-nacos-gateway

spring-nacos-provider

  1. 在pom.xml中添加依赖项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>com.alibaba.cloud</groupId>

<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

</dependency>

<dependency>

<groupId>com.alibaba.cloud</groupId>

<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-configuration-processor</artifactId>

<optional>true</optional>

</dependency>

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<optional>true</optional>

</dependency>
  1. 添加配置文件bootstrap.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html

# Nacos认证信息

spring.cloud.nacos.config.username=nacos

spring.cloud.nacos.config.password=nacos

spring.cloud.nacos.config.contextPath=/nacos

# 设置配置中心服务端地址

spring.cloud.nacos.config.server-addr=127.0.0.1:8848

# Nacos 配置中心的namespace,默认为 public

spring.cloud.nacos.config.namespace=public

spring.cloud.nacos.config.file-extension=yaml
  1. 修改配置文件application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:

port: 8081

spring:

application:

name: demo-service

cloud:

nacos:

discovery:

namespace: public

password: nacos

server-addr: 127.0.0.1:8848

username: nacos
  1. 添加一个HelloController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController

@Slf4j

public class HelloController {

@GetMapping("/foo")

public String foo(String name) {

return "hello "+ name;

}

}
  1. 启动项目后,访问http://localhost:8081/foo?name=张三,可以得到如下图示

image-20210423141827576

此时服务提供端已经配置好了

spring-nacos-gateway

  1. 在pom.xml中添加如下依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<dependency>

<groupId>com.alibaba.cloud</groupId>

<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

</dependency>

<dependency>

<groupId>com.alibaba.cloud</groupId>

<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-gateway</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-configuration-processor</artifactId>

<optional>true</optional>

</dependency>

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<optional>true</optional>

</dependency>

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.72</version>

</dependency>

<dependency>

<groupId>com.alibaba.csp</groupId>

<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>

<version>1.8.0</version>

</dependency>

<dependency>

<groupId>com.alibaba.cloud</groupId>

<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>

</dependency>

<dependency>

<groupId>com.alibaba.cloud</groupId>

<artifactId>spring-cloud-alibaba-sentinel-datasource</artifactId>

</dependency>
  1. 添加配置文件bootstrap.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html

# Nacos认证信息

spring.cloud.nacos.config.username=nacos

spring.cloud.nacos.config.password=nacos

spring.cloud.nacos.config.contextPath=/nacos

# 设置配置中心服务端地址

spring.cloud.nacos.config.server-addr=127.0.0.1:8848

# Nacos 配置中心的namespace,默认为 public

spring.cloud.nacos.config.namespace=public

spring.cloud.nacos.config.file-extension=yaml
  1. 修改配置文件application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
server:

port: 8080

spring:

application:

name: gateway

cloud:

nacos:

discovery:

namespace: public

password: nacos

server-addr: 127.0.0.1:8848

username: nacos

gateway:

discovery:

locator:

enabled: true

lower-case-service-id: true

routes:

- id: spring-nacos-provider

uri: lb://spring-nacos-provider

predicates:

- Path=/provider/**

filters:

- StripPrefix=1
  1. 启动应用,访问http://localhost:8080/provider/foo?name=张三

image-20210423142014725

完成网关接入。下面配合nacos,添加配置

在nacos中添加spring-nacos-provider.yaml的配置,注意选择类型为yaml

1
2
3
didi:

title: "架构师"

修改HelloController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController

@Slf4j

public class HelloController {

@Value("${didi.title}")

private String title;


@GetMapping("/foo")

public String foo(String name) {

return "hello "+ name + ", my title is " + title;

}

}

使用Ganache开发以太坊智能合约

下载Ganache

下载地址

安装Ganache

安装Metamask钱包

略,需要在Chrome中安装

创建或者导入metamask钱包

开发

导入测试钱包

在metamask钱包中添加自定义RPC链路

选择自定义RPC
image-20210423141024784

填写网络名称\URL\链ID
image-20210423141045865

URL填写http://127.0.0.1:7545

链ID填写0x1234

点击保存

导入钱包

找到账号的私钥
image-20210423141117898

image-20210423141131640

image-20210423141146283

输入私钥

image-20210423141156690

Dapp开发

https://github.com/MetaMask/detect-provider

https://docs.metamask.io/guide/ethereum-provider.html#using-the-provider

https://eips.ethereum.org/EIPS/eip-20

币安接口 https://binance-docs.github.io/apidocs/spot/cn/#45fa4e00db

多签钱包

ETH链的

ownbit

发布自己的ownbit多签钱包

https://ownbit.io/h5/app/prompt/pulish_ms_source_code_to_etherscan_zh.html

技术分析
https://www.chainnews.com/articles/438767464709.htm

ownbit

BSC链的

https://docs.binance.org/tss.html

https://docs.binance.org/smart-chain/developer/gnosis.html

2021学习计划

管理

  • 技术管理
    • 不断温习极客时间课程《技术管理实战36讲》
  • OKR
    • 学习 《黄勇的OKR实战笔记》

技术

  • Spring
    • 《小马哥讲Spring核心编程思想》
    • 《小马哥讲Spring AOP编程思想》
    • 《小马哥Java训练营》
    • 《玩转Spring全家桶》
  • ES
    • 《Elasticsearch核心技术与实战》
  • K8S
  • 架构
    • 《从0开始学架构》
    • 《周志明的软件架构课》
    • 《许式伟的架构课》
    • 《十年架构感悟》
    • 《DDD实战课》
  • 存储
    • 《后端存储实战课》
    • 《消息队列高手课》
    • 《MySQL实战45讲》
    • 《Redis核心技术与实战》
    • 《Kafka核心技术与实战》
    • 《ZooKeeper实战与源码剖析》

学习Docker

  1. Docker是什么
    Docker是一个容器

  2. Docker基本命令

    • 创建Ubuntu系统的容器 docker run -it ubuntu:14.04 /bin/bash
    • 查看当前所有容器 docker ps -a
    • 删除容器 docker rm {容器ID/名称}
    • 发布容器 docker commit
    • 绑定随机端口 docker run -d -P --name {容器名} ubuntu:14.04 /bin/bash
    • 绑定指定端口 docker run -d -p {本机ip}:{本机端口}:{容器端口} --name {容器名} ubuntu:14.04 /bin/bash
    • 挂载本机目录 docker run -d -v `pwd`:/webapp ubuntu:14.04 /bin/bash
  3. Dockerfile

    使用Dockerfile可以比较方便的统一完成容器的搭建、配置工作
    一个比较完整的文件示例如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #Version 1.0.1
    From ubuntu:14.04 #基本镜像

    MAINTAINER xxx "xxx@xxx.xx" #镜像作者信息

    #设置root用户为后续命令的执行者
    USER root

    #执行操作
    RUN apt-get update
    RUN apt-get install -y nginx

    #使用&&拼接命令
    RUN touch test.txt && echo 'test' >> test.txt

    #对外暴露端口
    EXPOSE 80 8080 9000

    #添加文件
    ADD abc.txt /opt
    #添加网络文件
    ADD http://xxx /opt

    #设置环境变量
    ENV WEB_PORT=80

    #设置工作目录
    WORKDIR /opt/

    #设置卷
    VOLUME ["/data", "/var/www"]

搭建视频播放服务

安装必备软件

  • libx264
    得到安装源代码 git clone git://git.videolan.org/x264.git
    编译
    1
    2
    ./configure --disable-yasm --enable-shared --enable-static
    make && make install

把so包增加到系统中

1
2
echo '/usr/local/lib/' >> /etc/ld.so.conf
ldconfig

使用

把mp4文件分割为多个ts文件和m3u8

1
ffmpeg -i 2001045000007.mp4 -c:v libx264 -c:a aac -strict -2 -f hls -hls_time 15 -hls_list_size 0 output.m3u8

-hls_time n: 设置每片的长度,默认值为2。单位为秒

-hls_list_size n:设置播放列表保存的最多条目,设置为0会保存有所片信息,默认值为5

-hls_wrap n:设置多少片之后开始覆盖,如果设置为0则不会覆盖,默认值为0.这个选项能够避免在磁盘上存储过多的片,而且能够限制写入磁盘的最多的片的数量

-hls_start_number n:设置播放列表中sequence number的值为number,默认值为0

注意:播放列表的sequence number 对每个segment来说都必须是唯一的,而且它不能和片的文件名(当使用wrap选项时,文件名有可能会重复使用)混淆

生成的m3u8文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
cat output.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:22
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:21.050000,
output0.ts
#EXTINF:11.000000,
output1.ts
#EXTINF:13.500000,
output2.ts
#EXTINF:14.500000,
output3.ts
#EXTINF:16.500000,
output4.ts
#EXTINF:19.600000,
output5.ts
#EXTINF:8.900000,
output6.ts
#EXTINF:14.950000,
output7.ts
#EXTINF:15.050000,
output8.ts
#EXTINF:15.600000,
output9.ts
#EXTINF:15.750000,
output10.ts
#EXTINF:14.000000,
output11.ts
#EXTINF:14.700000,
output12.ts
#EXTINF:15.350000,
output13.ts
#EXTINF:15.000000,
output14.ts
#EXTINF:10.600000,
output15.ts
#EXT-X-ENDLIST

搭建hls播放服务

  • 参考《做自己的m3u8点播系统使用HTTP Live Streaming(HLS技术)》

我的Rails开发总结

[TOC]

常用Gem列表

Rails Auth

  • devise 验证

  • cancancan 权限控制

Rails Frontend

  • kaminari 分页控件
1
2
3
4
5
6
7
8
@users = User.all.page(1).per_page(25)

currentPage: @users.current_page,
totalPages: @users.total_pages,
totalRows: @users.total_count,
hasNextPage: @users.next_page,
hasPreviousPage: @users.prev_page

  • SimpleForm 表单
1
需要执行rails generate simple_form:install --bootstrap
  • Popupload 图片上传
  • carrierwave-upyun 图片上传
  • mini_magick
  • carrierwave
  • carrierwave-meta
  • rest-client
  • mime-types
  • uuidtools

Rails Test Drive

  • ffaker 快速生成测试数据

Rails Helper

1
<%= local_time(comment.created_at) %>

Ruby General

CSS中引用assets中的图片

  1. 首先必须是scss或sass
  2. image-url(source) –> assets/images/sources

Rails中格式化时间

  1. Add gem ‘local_time’ to your Gemfile.
  2. Run bundle install.
  3. Add //= require local_time to your JavaScript manifest file (usually found at app/assets/javascripts/application.js).
1
2
<%= h local_time(notice.created_at, '%Y-%M-%d') %>
<%= local_time_ago(time) %>

使用Kindeditor

1、Gem ‘rails_kindeditor’
2、参考 rails_kindeditor

好用的bootstrap插件

  1. bootstrap-select
  2. bootstrap-filestyle

Jquery插件

  1. 全屏遮罩 blcokUI
  2. 大图预览 lightbox
  3. 图片滚动 cycle2

开发APP的步骤

1、Android签名文件、包名、签名字符串
2、IOS签名证书、包名
3、应用图标,16,28,32,64,80,100,108,120,512
4、应用截图 5张,含首页、关于我们、商品详情、支付
5、第三方应用注册申请
  • 微信

  • 微博

    微博需要应用简介图片
    微博上线前必须要有应用的下载地址(IOS必须在Appstore上线)

  • QQ

    Android 版本申请上线就会发布到 应用宝 市场

  • 友盟

6、微信支付
  • 签约资料准备
  • 商户认证
  • API安全密钥设置(需要安装操作证书)

Mysql5.5安装与配置方法

最新的Mysql5.5的源码安装已经不能再用configure的方式了,改成了cmake的编译方式,本文主要用于学习和记录mysql的基本安装方法。

1、下载源码

下载源码可以到mysql官网上下载,不过需要注意的是,一定要选择Source Code的方式,下载下来的文件为mysql-5.5.32.tar.gz

2、编译前的准备工作

编译前需要为系统安装一些其他工具支持,包括cmake,bison,gcc-c++,ncurses,automake,autoconf等

添加mysql用户和组

1
2
groupadd mysql
useradd -g mysql mysql -s /sbin/nologin

3、编译

编译参数可以参考文档

1
2
cmake -DCMAKE_INSTALL_PREFIX=/usr/local/mysql -DMYSQL_DATADIR=/usr/local/mysql/data -DWITH_INNOBASE_STORAGE_ENGINE=1 -DMYSQL_TCP_PORT=3306 -DMYSQL_UNIX_ADDR=/tmp/mysql.sock -DMYSQL_USER=mysql -DWITH_DEBUG=0 -DDEFAULT_CHAESET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -WITH_EXTRA_CHARSETS:STRING=utf8,gbk
make && make install

4、启动与停止

1
2
chown -R mysql:mysql /usr/local/mysql
scripts/mysql_install_db --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data

写一个启动停止的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash
mysql_port=3306
mysql_username="root"
mysql_passwd="123456"
mysql_basedir="/usr/local/mysql"
mysql_conf="/usr/local/mysql/conf"

function_start_mysql()
{
printf "Starting Mysql...\n"
/bin/sh ${mysql_basedir}/bin/mysqld_safe --defaults-file=${mysql_conf}f/my.cnf 2>&1 /dev/null &
}

function_stop_mysql()
{
printf "Stop Mysql...\n"
${mysql_basedir}/bin/mysqladmin -u ${mysql_username} -p${mysql_passwd} -S /tmp/mysql.sock shutdown
}

function_restart_mysql()
{
printf "Restarting Mysql...\n"
function_stop_mysql
sleep 5
function_start_mysql
}

if [ "$1" == "start" ]; then
function_start_mysql
elif [ "$1" == "stop" ]; then
function_stop_mysql
elif [ "$1" == "restart" ]; then
function_restart_mysql
else
printf "Usage: start|stop|restart\n"
fi