Commit 5c5cdd09 authored by Junling Bu's avatar Junling Bu
Browse files

update[deploy]: 进一步完善deploy。

1. 目前deploy支持dep阶段的代码部署
2. 目前deploy不支持prod阶段的代码部署。
parent 2abc3463
......@@ -39,14 +39,24 @@
http://xxx.xxx.xxx.xxx:8080/#/login
```
7. 自动上传脚本
7. 部署脚本
为了简化步骤1和步骤2,完成了util/upload.sh脚本,开发者需要设置相应的云主机IP和密钥文件路径。
该脚本会自动把当前项目不同模块下的最终部署文件复制到deploy文件夹中,然后上传到云主机。
为了简化步骤1和步骤2,完成了deploy/util/upload.sh上传脚本和deploy/util/lazy.sh部署脚本,
注意:
> 上传脚本没有自动做Spring Boot项目打包和Vue项目打包工作
> * 开发者需要在deploy/util/upload.sh和deploy/util/lazy.sh中设置相应的云主机登录账号和密钥文件路径。
> * 开发者需要在deploy/util/reset.sh设置云主机的MySQL的root登录账户。
> * 请先执行上述1-6步骤,确保部署环境成功。
* 上传脚本
该脚本会自动把当前项目不同模块下的最终部署文件复制到deploy文件夹中,然后上传到云主机。
该上传脚本没有自动做Spring Boot项目打包和Vue项目打包工作
* 部署脚本
该脚本会编译项目,再上传deploy文件,最后ssh登录远程主机执行bin下面的deploy.sh脚本。
如果开发者需要先编译项目再上传,可以运行util/lazy.sh。
注意,运行命令必须在项目主目录中,类似如下命令:
```bash
cd litemall
......
......@@ -3,8 +3,7 @@
# 请注意
# 1. 本脚本的作用是停止当前Spring Boot应用,然后再次部署
# 2. 解压dist.tar到/home/ubuntu/deploy/litemall-admin/dist,
# 而这个目录也正是tomcat配置静态文件目录的路径(见1.5.3.5节)
# 而这个目录也正是tomcat或者nginx所配置静态文件目录的路径(见1.5.3.5节)
#部署litemall-admin静态文件应用
cd /home/ubuntu/deploy/litemall-admin
......@@ -29,6 +28,6 @@ sudo /etc/init.d/litemall-os-api restart
sudo /etc/init.d/litemall-wx-api restart
sudo /etc/init.d/litemall-admin-api restart
# tomcat8服务也启动
sudo service tomcat8 stop
sudo service tomcat8 start
\ No newline at end of file
# 如果静态文件是通过tomcat来服务,则tomcat8服务最好也再启动
#sudo service tomcat8 stop
#sudo service tomcat8 start
\ No newline at end of file
......@@ -6,16 +6,23 @@
# 3.调用deploy.sh启动服务
# 注意:由于1和2的原因,请仅在开发测试阶段使用本脚本!
# 重置数据库
# i. 请在`XXXXXX`处设置相应的root密码
# ii. 同时请注意root密码放在脚本是非常危险的,因此这里仅仅是用于开发测试阶段。
ROOT=root
PASSWORD=
if test -z "$PASSWORD"
then
echo "请设置云主机MySQL的root账号密码"
exit -1
fi
cd /home/ubuntu/deploy/litemall-db
cat litemall_schema.sql > db.sql
cat litemall.sql >> db.sql
mysql -h localhost -u root -pXXXXXX < db.sql
mysql -h localhost -u $ROOT -p$PASSWORD < db.sql
rm db.sql
cd /home/ubuntu/deploy/litemall-db
# 删除storage文件夹内文件
cd /home/ubuntu/deploy/litemall-os-api/storage
......
RUN_ARGS=--spring.profiles.active=prod
RUN_ARGS=--spring.profiles.active=dep
JAVA_OPTS=
\ No newline at end of file
RUN_ARGS=--spring.profiles.active=prod
RUN_ARGS=--spring.profiles.active=dep
JAVA_OPTS=
\ No newline at end of file
RUN_ARGS=--spring.profiles.active=prod
RUN_ARGS=--spring.profiles.active=dep
JAVA_OPTS=
\ No newline at end of file
......@@ -2,12 +2,33 @@
# 本脚本的作用是
# 1. 编译打包Spring Boot应用
# 2. 编译litemall-adminy应用
# 2. 编译litemall-admin应用
# 3. 调用upload.sh上传
# 4. ssh远程登录云主机,运行deploy/bin/deploy.sh脚本
# 注意:运行脚本必须是在litemall主目录下,类似如下命令
# cd litemall
# ./deploy/util/lazy.sh
# 请设置云主机的IP地址和账户
# 例如 ubuntu@122.152.206.172
REMOTE=
# 请设置本地SSH私钥文件id_rsa路径
# 例如 /home/litemall/id_rsa
ID_RSA=
if test -z "$REMOTE"
then
echo "请设置云主机登录IP地址和账户"
exit -1
fi
if test -z "$ID_RSA"
then
echo "请设置云主机登录IP地址和账户"
exit -1
fi
echo $PWD
mvn clean
mvn package
......@@ -17,8 +38,14 @@ cd ./litemall-admin
npm install -g cnpm --registry=https://registry.npm.taobao.org
# 安装node项目依赖环境
cnpm install
cnpm run build:prod
cnpm run build:dep
cd ..
echo $PWD
./deploy/util/upload.sh
# 远程登录云主机并执行deploy脚本
ssh $REMOTE -i $ID_RSA << eeooff
sudo ./deploy/bin/deploy.sh
exit
eeooff
\ No newline at end of file
......@@ -6,10 +6,24 @@
# 3. util/upload.sh脚本是运行在开发机中,bin/deploy.sh脚本是运行在云主机中
# 4. 这是一个简单的脚本,开发者可以按照自己需求修改
# 请设置云主机的IP地址
CVM=XXX.XXX.XXX.XXX
# 请设置本地SSH私钥文件id_rsa
ID_RSA=/XXX/id_rsa
# 请设置云主机登录IP地址和账户
# 例如 ubuntu@122.152.206.172
REMOTE=
# 请设置本地SSH私钥文件id_rsa路径
# 例如 /home/litemall/id_rsa
ID_RSA=
if test -z "$REMOTE"
then
echo "请设置云主机登录IP地址和账户"
exit -1
fi
if test -z "$ID_RSA"
then
echo "请设置云主机登录IP地址和账户"
exit -1
fi
# 复制三个Spring Boot应用
# 需要注意的是target目录里面存在两种jar,一种是当前模块纯编译代码的jar,另外一种是包含依赖库的可执行jar,
......@@ -26,4 +40,4 @@ cp -f ./litemall-db/sql/litemall_schema.sql ./deploy/litemall-db/litemall_schem
cp -f ./litemall-db/sql/litemall.sql ./deploy/litemall-db/litemall.sql
# 上传云主机
scp -i $ID_RSA -r ./deploy ubuntu@$CVM:/home/ubuntu/
scp -i $ID_RSA -r ./deploy $REMOTE:/home/ubuntu/
......@@ -373,30 +373,31 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实
其次,需要明确的是各模块之间的关系:
* litemall-os-api模块会包含litemall-db模块,部署在服务器中
* litemall-wx-api模块会包含litemall-db模块,部署在服务器中
* litemall-admin-api模块会包含litemall-db模块,部署在服务器中
* litemall-wx模块部署在腾讯官方平台中,此外数据API地址指向litemall-wx-api所在服务qi地址
* litemall-os-api模块会包含litemall-core模块和litemall-db模块,部署在服务器中
* litemall-wx-api模块会包含litemall-core模块和litemall-db模块,部署在服务器中
* litemall-admin-api模块会包含litemall-core模块和litemall-db模块,部署在服务器中
* litemall-wx模块部署在微信开发者工具中,此外数据API地址指向litemall-wx-api所在服务qi地址
* litemall-admin编译出的静态文件放在web服务器或者tomcat服务器,此外服务器地址设置指向3中litemall-admin-api所在地址
注意
> * 这里litemall-os-api、litemall-admin-api和litemall-wx-api也可以选择编译成jar模式的可执行文件(因为内嵌tomcat服务器),然后直接运行。
> * litemall-wx正式部署时需要设置https开头的合法域名,因此litemall-wx-api所在的服务器需要配置合适的域名和SSL证书,具体参见官方文档。
> * litemall-wx部署时可以放在微信开发者工具中测试,但是上线时必须设置https开头的合法域名,因此litemall-wx-api所在的服务器需要配置合适的域名和SSL证书,
> 具体参见官方文档和本项目的1.6节。
最后,**如果项目正式部署,请一定要在以下文件中或代码中修改相应的配置。**
最后,**如果项目部署,则根据开发者的部署环境在以下文件中或代码中修改相应的配置。**
1. MySQL数据库设置合适的用户名和密码等信息,同时在litemall-os-api、litemall-wx-api和litemall-admin-api模块
的`resources/application-prod.properties` 中设置正确的数据库配置信息。
的`resources/application-dep.properties` 中设置正确的数据库配置信息。
2. litemall-wx模块`config/api.js`设置正确的`WxApiRoot`和`StorageApi`。
如果采用第三方对象存储服务,`StorageApi`指向第三方即可。
3. litemall-wx-api模块`config/WeixinConfig.java`中设置所申请的微信小程序APP和微信支付相应的信息。
4. litemall-os-api模块`resources/application-prod.properties` 中设置litemall-os-api服务所在的域名和端口地址
3. litemall-wx-api模块的`resources/application-dep.properties` 中设置所申请的微信小程序APPID和微信支付信息。
4. litemall-os-api模块`resources/application-dep.properties` 中设置litemall-os-api服务所在的IP和端口地址
```
org.linlinjava.litemall.os.address=http://XXX.com
org.linlinjava.litemall.os.port=80
org.linlinjava.litemall.os.address=http://xxx.xxx.xxx.xxx
org.linlinjava.litemall.os.port=8081
```
如果采用第三方对象存储服务,那么litemall-os-api可以不部署,这里的配置开忽略。
5. litemall-admin模块`config/prod.env.js`中设置正确的`BASE_API`和`OS_API`。
5. litemall-admin模块`config/dep.env.js`中设置正确的`BASE_API`和`OS_API`。
如果采用第三方对象存储服务,`OS_API`指向第三方即可。
实际上,最终的部署方案是灵活的:
......@@ -413,40 +414,6 @@ litemall是一个简单的商场系统,基于现有的开源项目,重新实
以下简单列举几种方案。
### 1.5.1 windows下本机测试部署方案
这里,我们是把window作为开发环境进行本项目的开发工作。
而项目开发完毕以后,在正式部署之前,可以先进行一个简单的本机测试部署方案。
首先,需要确保本地MySQL已经安装并且导入了litemall.sql数据;
其次,项目打包
```
cd litemall
mvn clean
mvn package
```
最后,本机测试性部署三个Spring Boot应用
```
cd litemall
java -jar ./litemall-os-api/target/litemall-os-api-0.1.0-exec.jar &
java -jar ./litemall-wx-api/target/litemall-wx-api-0.1.0-exec.jar &
java -jar ./litemall-admin-api/target/litemall-admin-api-0.1.0-exec.jar &
```
如果,能够访问以下链接的数据,则表明本地测试部署成功:
```
http://localhost:8081/os/index/index
http://localhost:8082/wx/index/index
http://localhost:8083/admin/index/index
```
注意
> 由于这里使用`&`设置成后台运行,因此测试结束以后,开发者需要自行通过任务管理器或其他软件关闭这三个后台Spring Boot应用。
### 1.5.2 简单局域网方案
局域网方案,面向的是最终服务器数据和部分应用程序部署在局域网内的场景。
### 1.5.3 基于ubuntu腾讯云的单机云部署方案
单机云部署方案,面向的是服务器数据和应用部署在云主机单机中用于演示的场景。
......@@ -656,7 +623,7 @@ mvn package
````bash
cd litemall/litemall-admin
cnpm run build:prod
cnpm run build:dep
````
此时,litemall-admin模块的dist文件夹中就是最终部署时的代码,可以先压缩,上传到云主机,再解压缩。
......@@ -707,39 +674,47 @@ https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#dep
http://xxx.xxx.xxx.xxx:8080/#/login
```
7. 自动上传脚本
#### 1.5.3.8 部署脚本
为了简化步骤1和步骤2,完成了deploy/util/upload.sh脚本,开发者需要设置相应的云主机IP和密钥文件路径。
该脚本会自动把当前项目不同模块下的最终部署文件复制到deploy文件夹中,然后上传到云主机。
注意:
> 上传脚本没有自动做Spring Boot项目打包和Vue项目打包工作
为了简化步骤1和步骤2,完成了deploy/util/upload.sh上传脚本和deploy/util/lazy.sh部署脚本,
注意:
> 1. 开发者需要在deploy/util/upload.sh和deploy/util/lazy.sh中设置相应的云主机登录账号和密钥文件路径。
> 2. 开发者需要在deploy/util/reset.sh设置云主机的MySQL的root登录账户。
> 3. 请先执行1.5.3中上述步骤,确保部署环境成功。
如下图所示,上传脚本自动上传deploy文件夹到云主机:
![](pic1/1-7.png)
需要指出的是,这里的upload.sh脚本是private文件夹中的文件,因为private文件夹是
在.gitignore中设置忽略,因此upload.sh脚本里面可以包含一些隐私信息,
如云主机IP和当前系统私钥文件地址,而其他内容则和deploy/util/upload.sh完全一致。
* 上传脚本
如果开发者需要先编译项目再上传,也可以运行util/lazy.sh。
注意,运行命令必须在项目主目录中,类似如下命令:
```bash
cd litemall
./deploy/util/lazy.sh
```
该脚本会自动把当前项目不同模块下的最终部署文件复制到deploy文件夹中,然后上传到云主机。
该上传脚本没有自动做Spring Boot项目打包和Vue项目打包工作
* 部署脚本
该脚本会编译项目,再上传deploy文件,最后ssh登录远程主机执行bin下面的deploy.sh脚本。
注意,运行命令必须在项目主目录中,类似如下命令:
```bash
cd litemall
./deploy/util/lazy.sh
```
### 1.5.4 分布式云部署方案
注意:
> 本项目的deploy文件夹以及其中的部署相关脚本只能适用于本节部署方式。
> 目前灵活性较差,开发者可以参考实现自己的相关脚本,简化开发工作。
### 1.5.4 集群式云部署方案
由于本项目是面向微小型企业的小商城系统,因此预期的分布式部署方案是
1. 专门的云数据库部署数据
2. 专门的云存储方案
3. 专门的CDN分发管理后台的静态文件
4. 一台云主机部署管理后台的后台服务
5. 一台云主机部署小商场的后台服务
5. 一台或多台云主机部署小商场的后台服务
虽然由于环境原因没有正式测试过,但是这种简单的分布式场景应该是可行的。
虽然由于环境原因没有正式测试过,但是这种简单的集群式场景应该是可行的。
在1.5.3节中所演示的四个服务是独立的,因此延伸到这里分布式是非常容易的。
但是,如果需要实现互联网式分布式云部署,目前的项目架构和方案会存在很多问题
但是,如果需要实现互联网式分布式云部署,目前的项目架构和方案不支持
至少每个功能模块应该是独立服务系统。此外,需要引入单点登录系统、集群、缓存
和消息队列等多种技术。因此如果开发者需要这种形式的分布式方案,请参考其他项目。
......@@ -747,6 +722,8 @@ https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#dep
这里介绍另外一种单主机单服务的方案,即四个服务打包成一个war格式的项目部署包。
![](pic1/1-11.png)
和1.5.3节相比,采用这样方案的原因是:
1. 安装方便,是传统的web项目安装方式,在tomcat里面部署一个war项目包,即可完成安装;
2. 内存消耗少。在1.5.3节中四个独立的java环境消耗的内存大概1.2G多,而这里部署以后
......@@ -768,7 +745,7 @@ https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#dep
## 1.6 上线方案
在1.5节部署方案中,我们介绍了多种部署的方案,但是实际上这些方案都不能直接用于正式环境:
在1.5节部署方案中,我们介绍了多种部署的方案,但是实际上这些方案都不能立即用于正式环境:
1. 正式环境需要域名和HTTPS证书
2. 小商场的小程序端对服务器域名存在接入要求。
......@@ -967,23 +944,44 @@ http://www.example.com
特别是`appid`要设置成开发者申请的appid。
### 1.6.4 上线脚本
### 1.6.4 后台服务上线
后台服务上线,则开发者需要自己的线上环境在以下文件中或代码中修改相应的配置。
1. MySQL数据库设置合适的用户名和密码等信息,同时在litemall-os-api、litemall-wx-api和litemall-admin-api模块
的`resources/application-prod.properties` 中设置正确的数据库配置信息。
2. litemall-wx模块`config/api.js`设置正确的`WxApiRoot`和`StorageApi`。
如果采用第三方对象存储服务,`StorageApi`指向第三方即可。
3. litemall-wx-api模块的`resources/application-prod.properties` 中设置所申请的微信小程序APPID和微信支付等信息。
4. litemall-os-api模块`resources/application-prod.properties` 中设置litemall-os-api服务所在的IP和端口地址
```
org.linlinjava.litemall.os.address=https://www.example.com
org.linlinjava.litemall.os.port=80
```
如果采用第三方对象存储服务,那么litemall-os-api可以不部署,这里的配置开忽略。
5. litemall-admin模块`config/prod.env.js`中设置正确的`BASE_API`和`OS_API`。
如果采用第三方对象存储服务,`OS_API`指向第三方即可。
### 1.6.5 上线脚本
本项目的deploy文件夹是用于
### 1.6.5 优化
### 1.6.6 优化
以下是部署方案中出现而在上线方案中可以优化的一些步骤。
#### 1.6.5.1 卸载tomcat
#### 1.6.6.1 卸载tomcat
如果部署方案中采用tomcat而8080端口访问后台,而这里配置nginx后,
可以直接采用80端口访问,因此tomcat可以卸载。
#### 1.6.5.2 静态文件托管CDN
#### 1.6.6.2 静态文件托管CDN
在上节中,建议采用卸载tomcat,采用nginx托管管理后台的静态文件。
这里可以进一步地,把静态文件托管到CDN,当然这里是需要收费。
#### 1.6.5.3 后台服务内部访问
#### 1.6.6.3 后台服务内部访问
原来后台服务可以通过域名或者IP直接对外服务,而这里采用nginx反向代理后可以
通过80端口访问后台服务。因此,会存在这样一种结果:
......@@ -994,7 +992,7 @@ http://www.example.com
而如果取消后台服务的对外访问,这样可以保证用户只能采用安全的https协议访问后台服务。
同时,对外也能屏蔽内部具体技术架构细节。
#### 1.6.5.4 nginx优化
#### 1.6.6.4 nginx优化
本人对nginx不是很熟悉,而nginx还存在很多可以调整优化的部分,这里建议开发者
根据自己业务或架构情况优化。
\ No newline at end of file
......@@ -136,7 +136,10 @@ litemall.sql数据库基于nideshop中的[nideshop.sql](https://github.com/tumob
* 费用信息
* 快递信息
目前快递信息仅仅记录快递公司、快递单号、快递发出时间、快递接收时间。而如果快递过程中如果存在一些异常,例如物品丢失,则目前系统难以处理。关于快递费的计算,目前采取简单方式,即满88元则免费,否则10元。
目前快递信息仅仅记录快递公司、快递单号、快递发出时间、快递接收时间。
而如果快递过程中如果存在一些异常,例如物品丢失,则目前系统难以处理。
关于快递费的计算,目前采取简单方式,即满88元则免费,否则10元。
* 支付信息
......@@ -149,70 +152,110 @@ litemall.sql数据库基于nideshop中的[nideshop.sql](https://github.com/tumob
订单分成几种基本的状态:
* 已下单
* 101
状态码101,此时订单生成,记录订单编号、收货地址信息、订单商品信息和订单相关费用信息;
状态码101,此时订单生成,记录订单编号、收货地址信息、订单商品信息和订单相关费用信息;
* 已付款
* 201
状态码201,此时用户微信支付付款,系统记录微信支付订单号、支付时间、支付状态;
状态码201,此时用户微信支付付款,系统记录微信支付订单号、支付时间、支付状态;
* 已发货
* 301
状态码301,此时商场已经发货,系统记录快递公司、快递单号、快递发送时间。
当快递公司反馈用户签收后,系统记录快递到达时间。
状态码301,此时商场已经发货,系统记录快递公司、快递单号、快递发送时间。
当快递公司反馈用户签收后,系统记录快递到达时间。
* 已确认
* 401
状态码401,当用户收到货以后点击确认收货,系统记录确认时间。
此时,用户可以评价订单商品。
状态码401,当用户收到货以后点击确认收货,系统记录确认时间。
除了这几种正常状态以外,还存在一些非普通的状态:
以上是一个订单成功完成的基本流程,但实际中还存在其他情况。
* 已取消
* 102
状态码102,用户下单后未付款之前,点击取消按钮,系统记录结束时间
* 系统自动取消
* 103
状态码103,用户下单后半小时未付款则系统自动取消,系统记录结束时间
* 已退款取消
* 202
状态码202,用户付款以后未发货前,点击退款按钮,系统进行设置退款状态,等待管理员退款操作
状态码202,用户付款以后未发货前,点击取消按钮,系统进行退款操作,并记录结束信息
* 203
* 系统自动确认
状态码203,管理员在管理后台看到用户的退款申请,则登录微信官方支付平台退款,然后回到
管理后台点击成功退款操作。
* 402
状态码402,快递反馈商场用户已签收而用户却不点击确认,超期7天以后,则系统自动确认收货
状态码402,用户已签收却不点击确认收货,超期7天以后,则系统自动确认收货
用户不能再点击确认收货按钮,但是可以评价订单商品。
* 501
* 502
* 503
此外,当订单状态码是102、103、203、401、402和503时,订单可以执行删除操作。
目前的设计是不执行物理删除,而是逻辑删除,因此用户查看自己订单时将看不到这些“已删除”的订单。
注意:
> 1. 目前退款相关功能未完成。
> 2. 在上图中可以看到`101`到`101`的状态变化,这里只是小商场用户的操作,不会影响订单状态码。
> * 用户点击付款时,后台服务会生成预支付会话id,但是不会影响订单状态。
> * 而用户支付过程中,放弃支付,例如没有
当然,以上的基本状态和非普通状态,和实际项目相比仍然相对简单。
#### 2.1.4.2 状态码所支持的用户操作
此外,当订单状态码是102、103、202、401、402时,订单可以执行删除操作。
虽然并没有最终真正删除,但是用户查看自己订单时将看不到这些“已删除”的订单。
状态码标识了订单的状态,但是对于用户而言,真正关心的只是他们能够进行的操作:
* `支付`,即下单后,用户可以进行支付
* `取消`,即用户未支付,可以取消当前订单
* `退款`,即用户支付后,可以申请退款
* `确认收货`,即用户收货以后,可以确认已收货
* `申请退货`,即用户确认收货以后,可以申请退货
* `评价`,即用户确认收货以后,可以对已购买商品评价
* `再次购买`,即用户确认收货以后,可以快速购买已购买过的商品
* `删除`,即用户可以想要删除自己的订单信息
#### 2.1.4.2 状态码所支持的操作
这些操作其实就是订单页面中的相应按钮,如果当前用户操作可以执行,则按钮就会出现。
不同的状态码下面,用户能够进行的操作是
因此订单状态码和小商场用户操作之间存在映射关系
* 101
用户可以“订单支付”、“订单取消”
用户可以`支付``取消`
* 102
用户可以“订单删除”
用户可以`删除`
* 103
用户可以“订单删除”
用户可以`删除`
* 201
用户可以“订单退款取消”
* 202
用户可以“订单删除”
用户可以`退款`
* 203
用户可以`删除`
* 301
用户可以“确认收货”
用户可以“确认收货”
* 401
用户可以“订单删除”、“评价”、“再次购买”
用户可以“订单删除”、“评价”、“再次购买”
* 402
用户可以“订单删除”、“评价”、“再次购买”
用户可以“订单删除”、“评价”、“再次购买”
#### 2.1.4.3 售后处理
......
doc/pic1/1-3.png

124 KB | W: | H:

doc/pic1/1-3.png

126 KB | W: | H:

doc/pic1/1-3.png
doc/pic1/1-3.png
doc/pic1/1-3.png
doc/pic1/1-3.png
  • 2-up
  • Swipe
  • Onion skin
doc/pic2/2-1.png

24.4 KB | W: | H:

doc/pic2/2-1.png

181 KB | W: | H:

doc/pic2/2-1.png
doc/pic2/2-1.png
doc/pic2/2-1.png
doc/pic2/2-1.png
  • 2-up
  • Swipe
  • Onion skin
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment