Unverified Commit 7023b205 authored by staney's avatar staney Committed by GitHub
Browse files

Merge pull request #1 from linlinjava/master

同步更新代码
parents c6ab92f9 506a8c01
# 更新日志
## V 1.5.0
*2019-11-15*, 持续优化轻商城模块,以及推荐项目Flutter_Mall
#### Bug 修复
* `小商城`优惠券绑定绑定优惠券ID(#157 by @pkwenda)
* `小商城`评论列表不能正确显示
* `轻商城`修正取消订单接口 (#256 by @1037621594)
#### 优化
* `小商城`采用延迟队列实现支付超时取消订单功能(参考#275 by @alexzhu0592)
* `小商城`分享按钮可选配置 (#239 by @galenzhao)
#### 新特性
* `基础系统`支持阿里云短信
* `轻商城`接入微信支付H5支付 (#291 by @pkwenda)
* `小商城`团购拼团超期取消 (#284 by @pkwenda)
* `管理后台`订单详情新增打印 (#274 by @fanchenggang )
* README文档推荐项目Flutter_Mall
## V 1.4.0
*2019-05-16*,支持移动端轻商城
......
......@@ -31,7 +31,7 @@ litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端
![](./doc/pic/mobmall.png)
或者浏览器采用手机模式访问以下网址: [http://118.24.0.153:8080/vue/index.html#/](http://118.24.0.153:8080/vue/index.html#/)
或者浏览器采用手机模式访问以下网址: [http://122.51.199.160:8080/vue/index.html#/](http://122.51.199.160:8080/vue/index.html#/)
注意:
> 1. 由于第一次加载数据量较大,建议wifi网络访问,且耐心等待数秒。
......@@ -41,7 +41,7 @@ litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端
![](./doc/pic/4.png)
1. 浏览器打开,输入以下网址: [http://118.24.0.153:8080/#/login](http://118.24.0.153:8080/#/login)
1. 浏览器打开,输入以下网址: [http://122.51.199.160:8080/#/login](http://122.51.199.160:8080/#/login)
2. 管理员用户名`admin123`,管理员密码`admin123`
> 注意:此实例只是测试管理后台,不是前两个小商城的管理后台。
......@@ -154,7 +154,7 @@ litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端
## 开发计划
当前版本[v1.4.0](https://linlinjava.gitbook.io/litemall/changelog)
当前版本[v1.5.0](https://linlinjava.gitbook.io/litemall/changelog)
目前项目开发中,存在诸多不足,以下是目前规划的开发计划。
......@@ -219,7 +219,13 @@ V 3.0.0 完成以下目标:
项目介绍:基于有赞 vant 组件库的移动商城。
项目参考:litemall项目的litemall-vue模块基于vant--mobile-mall项目开发。
## 推荐
1. [Flutter_Mall](https://github.com/youxinLu/mall)
项目介绍:Flutter_Mall是一款Flutter开源在线商城应用程序。
## 问题
![](doc/pic/qq2.png)
......
......@@ -2,7 +2,7 @@
### 项目打包
1.主机或者开发机打包项目到deploy;
1.服务器或者开发机打包项目到deploy;
```
cd litemall
cat ./litemall-db/sql/litemall_schema.sql > ./deploy/db/litemall.sql
......@@ -25,13 +25,13 @@
2. 修改litemall文件夹下面的*.yml外部配置文件,当litemall-all模块启动时会
加载外部配置文件,而覆盖默认jar包内部的配置文件。
例如,配置文件中一些地方需要设置成远程主机的IP地址
例如,配置文件中一些地方需要设置成远程服务器的IP地址
此时deploy部署包结构如下:
* bin
存放远程主机运行的脚本,包括deploy.sh脚本和reset.sh脚本
存放远程服务器运行的脚本,包括deploy.sh脚本和reset.sh脚本
* db
......@@ -39,15 +39,15 @@
* litemall
存放远程主机运行的代码,包括litemall-all二进制可执行包和litemall外部配置文件
存放远程服务器运行的代码,包括litemall-all二进制可执行包和litemall外部配置文件
* util
存放开发主机运行的脚本,包括package.sh脚本和lazy.sh脚本。
由于是本地开发主机运行,因此开发者可以不用上传到远程主机
存放开发服务器运行的脚本,包括package.sh脚本和lazy.sh脚本。
由于是本地开发服务器运行,因此开发者可以不用上传到远程服务器
### 项目部署
1. 远程主机环境(MySQL和JDK1.8)已经安装好,请确保云主机的安全组已经允许相应的端口。
1. 远程服务器环境(MySQL和JDK1.8)已经安装好,请确保云服务器的安全组已经允许相应的端口。
2. 导入db/litemall.sql
```bash
cd /home/ubuntu/deploy/db
......@@ -59,7 +59,7 @@
sudo ln -f -s /home/ubuntu/deploy/litemall/litemall.jar /etc/init.d/litemall
sudo service litemall start
```
4. 测试是否部署成功(xxx.xxx.xxx.xxx是云主机IP):
4. 测试是否部署成功(xxx.xxx.xxx.xxx是云服务器IP):
```
http://xxx.xxx.xxx.xxx:8080/wx/index/index
http://xxx.xxx.xxx.xxx:8080/admin/index/index
......@@ -73,26 +73,26 @@
* util/packet.sh
在开发主机运行可以自动项目打包
在开发服务器运行可以自动项目打包
* util/lazy.sh
在开发主机运行可以自动项目打包、项目上传远程主机、自动登录系统执行项目部署脚本。
在开发服务器运行可以自动项目打包、项目上传远程服务器、自动登录系统执行项目部署脚本。
注意:
> 1. 开发者需要在util/lazy.sh中设置相应的远程主机登录账号和密钥文件路径。
> 2. 开发者需要在bin/reset.sh设置远程主机的MySQL的root登录账户。
> 1. 开发者需要在util/lazy.sh中设置相应的远程服务器登录账号和密钥文件路径。
> 2. 开发者需要在bin/reset.sh设置远程服务器的MySQL的root登录账户。
* bin/deploy.sh
在远程主机运行可以自动部署服务
在远程服务器运行可以自动部署服务
* bin/reset.sh
在远程主机运行可以自动项目导入数据、删除本地上传图片、再执行bin/deploy.sh部署服务。
在远程服务器运行可以自动项目导入数据、删除本地上传图片、再执行bin/deploy.sh部署服务。
注意:
> 开发者需要在bin/reset.sh设置远程主机的MySQL的root登录账户。
> 开发者需要在bin/reset.sh设置远程服务器的MySQL的root登录账户。
总结,当开发者设置好配置信息以后,可以在本地运行lazy.sh脚本自动一键部署:
```bash
......
......@@ -14,7 +14,7 @@ PASSWORD=
if test -z "$PASSWORD"
then
echo "请设置云主机MySQL的root账号密码"
echo "请设置云服务器MySQL的root账号密码"
exit 1
fi
......
......@@ -41,7 +41,7 @@ litemall:
app-secret: e04004829d4c383b4db7769d88dfbca1
mch-id: 111111
mch-key: xxxxxx
notify-url: http://118.24.0.153:8080/wx/order/pay-notify
notify-url: http://122.51.199.160:8080/wx/order/pay-notify
# 商户证书文件路径
# 请参考“商户证书”一节 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
key-path: xxxxx
......@@ -61,8 +61,10 @@ litemall:
# 短信息用于通知客户,例如发货短信通知,注意配置格式;template-name,template-templateId 请参考 NotifyType 枚举值
sms:
enable: false
appid: 111111111
appkey: xxxxxxxxxxxxxx
# 如果是腾讯云短信,则设置active的值tencent
# 如果是阿里云短信,则设置active的值aliyun
active: tencent
sign: litemall
template:
- name: paySucceed
templateId: 156349
......@@ -72,6 +74,13 @@ litemall:
templateId: 158002
- name: refund
templateId: 159447
tencent:
appid: 111111111
appkey: xxxxxxxxxxxxxx
aliyun:
regionId: xxx
accessKeyId: xxx
accessKeySecret: xxx
# 微信模版通知配置
# 微信模版用于通知客户或者运营者,注意配置格式;template-name,template-templateId 请参考 NotifyType 枚举值
......@@ -127,7 +136,7 @@ litemall:
# 本地对象存储配置信息
local:
storagePath: storage
address: http://118.24.0.153:8080/wx/storage/fetch/
address: http://122.51.199.160:8080/wx/storage/fetch/
# 阿里云对象存储配置信息
aliyun:
endpoint: oss-cn-shenzhen.aliyuncs.com
......
......@@ -2,11 +2,11 @@
# 本脚本的作用是
# 1. 项目打包
# 2. 上传云主机
# 3. 远程登录云主机并执行reset脚本
# 2. 上传云服务器
# 3. 远程登录云服务器并执行reset脚本
# 请设置云主机的IP地址和账户
# 例如 ubuntu@118.24.0.153
# 请设置云服务器的IP地址和账户
# 例如 ubuntu@122.51.199.160
REMOTE=
# 请设置本地SSH私钥文件id_rsa路径
# 例如 /home/litemall/id_rsa
......@@ -14,13 +14,13 @@ ID_RSA=
if test -z "$REMOTE"
then
echo "请设置云主机登录IP地址和账户"
echo "请设置云服务器登录IP地址和账户"
exit 1
fi
if test -z "$ID_RSA"
then
echo "请设置云主机登录IP地址和账户"
echo "请设置云服务器登录IP地址和账户"
exit 1
fi
......@@ -33,11 +33,11 @@ echo "LITEMALL_HOME $LITEMALL_HOME"
cd $LITEMALL_HOME || exit 2
./deploy/util/package.sh
# 上传云主机
# 上传云服务器
cd $LITEMALL_HOME || exit 2
scp -i $ID_RSA -r ./deploy $REMOTE:/home/ubuntu/
# 远程登录云主机并执行reset脚本
# 远程登录云服务器并执行reset脚本
ssh $REMOTE -i $ID_RSA << eeooff
cd /home/ubuntu
sudo ./deploy/bin/reset.sh
......
......@@ -1153,7 +1153,7 @@ public interface Storage {
## 2.4 litemall-all
在章节1.5中讨论的部署方案中设计了一种单主机单服务方案,
在章节1.5中讨论的部署方案中设计了一种单服务器单服务方案,
也就是说两个后台服务和静态文件都部署在一个Spring Boot可执行jar包中。
查看litemall-all模块,代码仅仅只有一个Application类。
......
......@@ -243,7 +243,7 @@ Spring Boot技术栈参考以下文档或者项目:
* dep
即deploy或者deployment,这里指部署(测试阶段),通常代码已经编译打包运行在远程主机中,
即deploy或者deployment,这里指部署(测试阶段),通常代码已经编译打包运行在远程服务器中,
可以对外服务。此外,这里服务访问地址通常是IP地址。如果IP是公网IP,那么
部署以后就可以对外服务;如果是内网地址,那么只能内网访问。这里的“用户”主要是
指开发者本身、测试者;当然,如果是局域网或者不介意IP访问的,那么这里的“用户”
......@@ -251,7 +251,7 @@ Spring Boot技术栈参考以下文档或者项目:
* prod
即product或者production,这里指上线阶段,通常也是代码编译打包运行在远处主机中可以对外服务。
即product或者production,这里指上线阶段,通常也是代码编译打包运行在远处服务器中可以对外服务。
此外,这里服务访问地址通常是域名地址,同时端口是80web端口。上线以后直接面向的是最终用户。
虽然服务的代码本身和dep是完全一样的,但是考虑到场景的不同,上线阶段可能在运行环境方面需要做
调整,例如采用反向代理屏蔽内部实际项目结构。此外,最大的不同应该是上线环境下要使用域名和80端口,
......@@ -435,7 +435,7 @@ flush privilege
// 局域网测试使用
// var WxApiRoot = 'http://192.168.0.101:8080/wx/';
// 云平台部署时使用
// var WxApiRoot = 'http://118.24.0.153:8080/wx/';
// var WxApiRoot = 'http://122.51.199.160:8080/wx/';
// 云平台上线时使用
// var WxApiRoot = 'https://www.menethil.com.cn/wx/';
......@@ -589,24 +589,33 @@ litemall:
# 短信息用于通知客户,例如发货短信通知,注意配置格式;template-name,template-templateId 请参考 NotifyType 枚举值
sms:
enable: false
appid: 111111111
appkey: xxxxxxxxxxxxxx
# 如果是腾讯云短信,则设置active的值tencent
# 如果是阿里云短信,则设置active的值aliyun
active: tencent
sign: litemall
template:
- name: paySucceed
templateId: 156349
- name: captcha
templateId: 156433
- name: ship
templateId: 158002
- name: refund
templateId: 159447
- name: paySucceed
templateId: 156349
- name: captcha
templateId: 156433
- name: ship
templateId: 158002
- name: refund
templateId: 159447
tencent:
appid: 111111111
appkey: xxxxxxxxxxxxxx
aliyun:
regionId: xxx
accessKeyId: xxx
accessKeySecret: xxx
```
配置方式:
1. 腾讯云短信平台申请,然后设置四个场景的短信模板;
2. 开发者在配置文件设置`enable`的值`true`,然后其他信息设置
腾讯云短信平台申请的appid等值。
这里只测试过腾讯云短信平台,开发者需要自行测试其他短信云平台。
1. 腾讯云短信平台或者阿里云短信平台申请,然后设置四个场景的短信模板;
2. 开发者在配置文件设置`enable`的值`true`,设置`active`的值`tencent`或`aliyun`
3. 然后配置其他信息,例如腾讯云短信平台申请的appid等值。
这里只测试过腾讯云短信平台和阿里云短信平台,开发者需要自行测试其他短信云平台。
应用场景:
目前短信通知场景只支持支付成功、验证码、订单发送、退款成功四种情况。
......@@ -616,6 +625,17 @@ litemall:
当配置好信息以后,开发者可以litemall-core模块的`SmsTest`测试类中设置手机号和
模板所需要的参数值,独立启动`SmsTest`测试类发送短信,然后查看手机是否成功接收短信。
短信模板参数命名:
这里存在一个问题,即腾讯云短信的官方平台中申请短信模板格式的模板参数是数组,
例如“你好,验证码是{0},时间是{1}";
而阿里云短信的官方平台中申请短信模板的模板参数是JSON,
例如“你好,验证码是{param1},时间是{param2}"。
为了保持当前代码的通用性,本项目采用数组传递参数,而对阿里云申请模板的参数做了一定的假设:
1. 腾讯云模块参数,申请模板时按照官方设置即可,例如“你好,验证码是{0},时间是{1}";
2. 阿里云模板参数,本项目假定开发者在官方申请的参数格式应该采用"{ code: xxx, code1: xxx, code2: xxx }",
例如“你好,验证码是{code},时间是{code1}"。开发者可以查看`AliyunSmsSender`类的`sendWithTemplate`方法的
源代码即可理解。如果觉得不合理,可以自行调整相关代码。
#### 1.4.5.7 微信通知配置
微信通知是微信上收到的服务通知。
......@@ -717,7 +737,7 @@ litemall:
在litemall-core模块的`application-core.yml`文件中配置对象存储服务:
* 本地对象存储配置
如果开发者采用当前主机保存上传的文件,则需要配置:
如果开发者采用当前服务器保存上传的文件,则需要配置:
```
litemall:
storage:
......@@ -810,7 +830,7 @@ litemall:
* litemall-wx模块部署在微信开发者工具中,此外数据API地址指向litemall-wx-api所在服务qi地址
* litemall-admin编译出的静态文件放在web服务器或者tomcat服务器,此外服务器地址设置指向3中litemall-admin-api所在地址
最后,**如果项目部署云主机,则根据开发者的部署环境在以下文件中或代码中修改相应的配置。**
最后,**如果项目部署云服务器,则根据开发者的部署环境在以下文件中或代码中修改相应的配置。**
1. MySQL数据库设置合适的用户名和密码信息;
2. 后端服务模块设置合适的配置信息;
......@@ -819,33 +839,34 @@ litemall:
实际上,最终的部署方案是灵活的:
* 可以是同一云主机中安装一个Spring Boot服务,同时提供litemall-admin、litemall-admin-api和litemall-wx-api三种服务
* 可以单一云主机中仅安装一个tomcat/nginx服务器部署litemall-admin静态页面分发服务,
* 可以是同一云服务器中安装一个Spring Boot服务,同时提供litemall-admin、litemall-admin-api和litemall-wx-api三种服务
* 可以单一云服务器中仅安装一个tomcat/nginx服务器部署litemall-admin静态页面分发服务,
然后部署两个Spring Boot的后端服务;
* 也可以把litemall-admin静态页面托管第三方cdn,然后开发者部署两个后端服务
* 当然,甚至多个服务器,采用集群式并发提供服务。
注意
> 1. `本机`指的是是当前的开发机
> 2. `云主机`指的是开发者购买并部署的远程主机
> 2. `云服务器`指的是开发者购买并部署的远程服务器
以下简单列举几种方案。
### 1.5.1 单机单服务部署方案
本节介绍基于腾讯云的单机单服务部署方案,面向的是服务器数据和应用部署在云主机单机中用于演示的场景。
本节介绍基于腾讯云的单机单服务部署方案,面向的是服务器数据和应用部署在云服务器单机中用于演示的场景。
其他云应该也是可行的。
主要流程是:创建云主机,安装ubuntu操作系统,按照JDK和MySQL应用运行环境,部署单一Spring Boot服务。
主要流程是:创建云服务器,安装ubuntu操作系统,按照JDK和MySQL应用运行环境,部署单一Spring Boot服务。
![](pic1/1-11.png)
#### 1.5.1.1 主机
请参考腾讯云官方文档进行相关操作。
#### 1.5.1.1 云服务器
1. 创建云主机虚拟机
1. 创建云服务器
请参考腾讯云、阿里云或者其他云平台的官方文档进行相关操作。
建议最低配置是**1核2G**。
2. 安装操作系统
本项目采用ubuntu 16.04.1,但是并不限制其他操作系统。
......@@ -854,48 +875,55 @@ litemall:
![](pic1/1-4.png)
目前允许的端口:8081,8082,8083,8080,80,443,22,3306
目前允许的端口:8080,80,443,22,3306
注意:
这里其实只需要8080端口,允许其他端口只是方面开发阶段的测试和调试。
这里其实只需要8080端口,允许其他端口只是方便开发阶段的测试和调试。
特别是3306端口,作为MySQL的远程访问端口,请在上线阶段关闭。
4. 设置SSH密钥(可选)
建议开发者设置SSH密钥,可以免密码登录云主机,以及用于脚本自动上传应用。
建议开发者设置SSH密钥,可以免密码登录云服务器,以及用于脚本自动上传应用。
5. 使用PuTTY远程登录云主机
5. 使用PuTTY远程登录云服务器
如果开发者设置SSH密钥,可以采用免密码登录;否则采用账号和密码登录。
#### 1.5.1.2 JDK8
#### 1.5.1.2 OpenJDK8
https://www.digitalocean.com/community/tutorials/how-to-install-java-with-apt-get-on-ubuntu-16-04
这里可以安装openjdk-8-jre
http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html
```bash
sudo apt-get update
sudo apt-get install openjdk-8-jre
```
如果希望采用jdk,而不是jre,则可以运行
```bash
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer
sudo apt-get install oracle-java8-set-default
sudo apt-get install openjdk-8-jdk
```
警告
> "ppa:webupd8team/java" 不是Oracle官方PPA,可能存在安全隐患
注意
> 如果用户想采用Oracle JDK8或者其他JDK环境,请查阅相关资料安装
#### 1.5.1.3 MySQL
https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-16-04
```
sudo apt-get update
sudo apt-get install mysql-server
sudo apt-get install mysql-client
```
如果配置MySQL,可以运行命令
```
sudo mysql_secure_installation
```
#### 1.5.1.4 项目打包
1. 在主机或者开发机打包项目到deploy;
1. 在服务器或者开发机打包项目到deploy;
```
cd litemall
cat ./litemall-db/sql/litemall_schema.sql > ./deploy/db/litemall.sql
......@@ -920,26 +948,26 @@ sudo apt-get install mysql-client
2. 修改litemall文件夹下面的*.yml外部配置文件,当litemall-all模块启动时会
加载外部配置文件,而覆盖默认jar包内部的配置文件。
例如,配置文件中一些地方需要设置成远程主机的IP地址
例如,配置文件中一些地方需要设置成远程服务器的IP地址
此时deploy部署包结构如下:
* bin
存放远程主机运行的脚本,包括deploy.sh脚本和reset.sh脚本
存放远程服务器运行的脚本,包括deploy.sh脚本和reset.sh脚本
* db
存放litemall数据库文件
* litemall
存放远程主机运行的代码,包括litemall-all二进制可执行包和litemall外部配置文件
存放远程服务器运行的代码,包括litemall-all二进制可执行包和litemall外部配置文件
* util
存放开发主机运行的脚本,包括package.sh脚本和lazy.sh脚本。
由于是本地开发主机运行,因此开发者可以不用上传到远程主机
存放开发服务器运行的脚本,包括package.sh脚本和lazy.sh脚本。
由于是本地开发服务器运行,因此开发者可以不用上传到远程服务器
#### 1.5.1.5 项目部署
1. 远程主机环境(MySQL和JDK1.8)已经安装好,请确保云主机的安全组已经允许相应的端口。
1. 远程服务器环境(MySQL和JDK1.8)已经安装好,请确保云服务器的安全组已经允许相应的端口。
2. 导入db/litemall.sql
```bash
cd /home/ubuntu/deploy/db
......@@ -951,7 +979,7 @@ sudo apt-get install mysql-client
sudo ln -f -s /home/ubuntu/deploy/litemall/litemall.jar /etc/init.d/litemall
sudo service litemall start
```
4. 测试是否部署成功(xxx.xxx.xxx.xxx是云主机IP):
4. 测试是否部署成功(xxx.xxx.xxx.xxx是云服务器IP):
```
http://xxx.xxx.xxx.xxx:8080/wx/index/index
http://xxx.xxx.xxx.xxx:8080/admin/index/index
......@@ -970,26 +998,26 @@ sudo apt-get install mysql-client
* util/packet.sh
在开发主机运行可以自动项目打包
在开发服务器运行可以自动项目打包
* util/lazy.sh
在开发主机运行可以自动项目打包、项目上传远程主机、自动登录系统执行项目部署脚本。
在开发服务器运行可以自动项目打包、项目上传远程服务器、自动登录系统执行项目部署脚本。
注意:
> 1. 开发者需要在util/lazy.sh中设置相应的远程主机登录账号和密钥文件路径。
> 2. 开发者需要在bin/reset.sh设置远程主机的MySQL的root登录账户。
> 1. 开发者需要在util/lazy.sh中设置相应的远程服务器登录账号和密钥文件路径。
> 2. 开发者需要在bin/reset.sh设置远程服务器的MySQL的root登录账户。
* bin/deploy.sh
在远程主机运行可以自动部署服务
在远程服务器运行可以自动部署服务
* bin/reset.sh
在远程主机运行可以自动项目导入数据、删除本地上传图片、再执行bin/deploy.sh部署服务。
在远程服务器运行可以自动项目导入数据、删除本地上传图片、再执行bin/deploy.sh部署服务。
注意:
> 开发者需要在bin/reset.sh设置远程主机的MySQL的root登录账户。
> 开发者需要在bin/reset.sh设置远程服务器的MySQL的root登录账户。
总结,当开发者设置好配置信息以后,可以在本地运行lazy.sh脚本自动一键部署:
```bash
......@@ -1010,8 +1038,8 @@ cd litemall
1. 专门的云数据库部署数据
2. 专门的云存储方案
3. 专门的CDN分发管理后台的静态文件
4. 一台云主机部署管理后台的后端服务
5. 一台或多台云主机部署小商场的后端服务
4. 一台云服务器部署管理后台的后端服务
5. 一台或多台云服务器部署小商场的后端服务
虽然由于环境原因没有正式测试过,但是这种简单的集群式场景应该是可行的。
在1.5.2节中所演示的三个服务是独立的,因此延伸到这里分布式是非常容易的。
......@@ -1060,7 +1088,7 @@ sudo apt-get update
sudo apt-get install nginx
```
有的文档会指出需要防火墙设置,但是腾讯云主机防火墙默认没有开启。
有的文档会指出需要防火墙设置,但是腾讯云服务器防火墙默认没有开启。
开发者这里自己可以开启设置,或者直接不开启。
打开浏览器,输入以下地址:
......@@ -1330,12 +1358,12 @@ litemall-admin编译得到的前端文件在第一次加载时相当耗时,这
#### 1.7.2.1 deploy部署
当前项目存在deploy部署文件夹,这个是上述1.5.1节部署腾讯云主机所采取的一些脚本。
当前项目存在deploy部署文件夹,这个是上述1.5.1节部署腾讯云服务器所采取的一些脚本。
流程如下:
1. util脚本是当前开发主机运行,用来打包项目和上传腾讯云主机
1. util脚本是当前开发服务器运行,用来打包项目和上传腾讯云服务器
2. 打包项目时,会编译打包项目相关模块到litemall和db文件夹中;
3. bin脚本是云主机运行,用来安装数据库、导入数据、启动项目服务。
3. bin脚本是云服务器运行,用来安装数据库、导入数据、启动项目服务。
这里deploy部署方式比较简单不灵活,开发者可以参考开发自己的项目脚本。
......
......@@ -83,9 +83,9 @@
2. 启动后台服务
3. 部署后台服务到云主机
3. 部署后台服务到云服务器
4. litemall-wx的api.js设置云主机的域名。
4. litemall-wx的api.js设置云服务器的域名。
编译运行,尝试微信支付。
### 3.0.3 微信退款配置
......@@ -236,14 +236,14 @@ var WxApiRoot = 'http://localhost:8082/wx/';
// 局域网测试使用
// var WxApiRoot = 'http://192.168.0.101:8082/wx/';
// 云平台部署时使用
// var WxApiRoot = 'http://118.24.0.153:8082/wx/';
// var WxApiRoot = 'http://122.51.199.160:8082/wx/';
```
也就是说这里存在三种类型的API服务地址,这里是考虑到开发存在三种情况:
1. 本机开发时,localhost是当前开发机的地址;
2. 手机预览时,192.168.0.101是开发机的IP地址;
3. 当后台部署在云主机中时,118.24.0.153是云主机的IP地址;
3. 当后台部署在云服务器中时,122.51.199.160是云服务器的IP地址;
4. 此外,更最重要的是,如果小程序正式部署时,这里的地址必须是域名,
而不能是IP地址。
......
......@@ -3,11 +3,8 @@ package org.linlinjava.litemall.admin.job;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.linlinjava.litemall.core.system.SystemConfig;
import org.linlinjava.litemall.db.domain.LitemallOrder;
import org.linlinjava.litemall.db.domain.LitemallOrderGoods;
import org.linlinjava.litemall.db.service.LitemallGoodsProductService;
import org.linlinjava.litemall.db.service.LitemallOrderGoodsService;
import org.linlinjava.litemall.db.service.LitemallOrderService;
import org.linlinjava.litemall.db.domain.*;
import org.linlinjava.litemall.db.service.*;
import org.linlinjava.litemall.db.util.OrderUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
......@@ -30,43 +27,10 @@ public class OrderJob {
private LitemallOrderService orderService;
@Autowired
private LitemallGoodsProductService productService;
/**
* 自动取消订单
* <p>
* 定时检查订单未付款情况,如果超时 LITEMALL_ORDER_UNPAID 分钟则自动取消订单
* 定时时间是每次相隔半个小时。
* <p>
* TODO
* 注意,因为是相隔半小时检查,因此导致订单真正超时时间是 [LITEMALL_ORDER_UNPAID, 30 + LITEMALL_ORDER_UNPAID]
*/
@Scheduled(fixedDelay = 30 * 60 * 1000)
@Transactional(rollbackFor = Exception.class)
public void checkOrderUnpaid() {
logger.info("系统开启任务检查订单是否已经超期自动取消订单");
List<LitemallOrder> orderList = orderService.queryUnpaid(SystemConfig.getOrderUnpaid());
for (LitemallOrder order : orderList) {
// 设置订单已取消状态
order.setOrderStatus(OrderUtil.STATUS_AUTO_CANCEL);
order.setEndTime(LocalDateTime.now());
if (orderService.updateWithOptimisticLocker(order) == 0) {
throw new RuntimeException("更新数据已失效");
}
// 商品货品数量增加
Integer orderId = order.getId();
List<LitemallOrderGoods> orderGoodsList = orderGoodsService.queryByOid(orderId);
for (LitemallOrderGoods orderGoods : orderGoodsList) {
Integer productId = orderGoods.getProductId();
Short number = orderGoods.getNumber();
if (productService.addStock(productId, number) == 0) {
throw new RuntimeException("商品货品库存增加失败");
}
}
logger.info("订单 ID" + order.getId() + " 已经超期自动取消订单");
}
}
@Autowired
private LitemallGrouponService grouponService;
@Autowired
private LitemallGrouponRulesService rulesService;
/**
* 自动确认订单
......@@ -79,7 +43,7 @@ public class OrderJob {
*/
@Scheduled(cron = "0 0 3 * * ?")
public void checkOrderUnconfirm() {
logger.info("系统开启任务检查订单是否已经超期自动确认收货");
logger.info("系统开启定时任务检查订单是否已经超期自动确认收货");
List<LitemallOrder> orderList = orderService.queryUnconfirm(SystemConfig.getOrderUnconfirm());
for (LitemallOrder order : orderList) {
......@@ -120,4 +84,55 @@ public class OrderJob {
}
}
}
/**
* 团购订单拼团超期自动取消
*/
@Scheduled(initialDelay = 5000, fixedDelay = 10 * 60 * 1000)
@Transactional(rollbackFor = Exception.class)
public void checkGrouponOrderTimeout() {
logger.info("系统开启定时任务检查团购订单是否已经拼团超期自动取消订单");
List<LitemallGroupon> grouponList = grouponService.queryJoinRecord(0);
for (LitemallGroupon groupon : grouponList) {
LitemallGrouponRules rules = rulesService.queryById(groupon.getRulesId());
if (rulesService.isExpired(rules)) {
List<LitemallGroupon> subGrouponList = grouponService.queryJoinRecord(groupon.getId());
for (LitemallGroupon subGroupon : subGrouponList) {
cancelGrouponScope(subGroupon);
}
cancelGrouponScope(groupon);
}
}
}
private void cancelGrouponScope(LitemallGroupon groupon) {
LitemallOrder order = orderService.findById(groupon.getOrderId());
if (order.getOrderStatus().equals(OrderUtil.STATUS_PAY_GROUPON)) {
order.setOrderStatus(OrderUtil.STATUS_TIMEOUT_GROUPON);
order.setEndTime(LocalDateTime.now());
cancelOrderScope(order);
logger.info("团购订单 ID" + order.getId() + " 已经拼团超期自动取消订单");
}
}
private void cancelOrderScope(LitemallOrder order) {
if (orderService.updateWithOptimisticLocker(order) == 0) {
throw new RuntimeException("更新数据已失效");
}
// 商品货品数量增加
Integer orderId = order.getId();
List<LitemallOrderGoods> orderGoodsList = orderGoodsService.queryByOid(orderId);
for (LitemallOrderGoods orderGoods : orderGoodsList) {
Integer productId = orderGoods.getProductId();
Short number = orderGoods.getNumber();
if (productService.addStock(productId, number) == 0) {
throw new RuntimeException("商品货品库存增加失败");
}
}
}
}
module.exports = {
NODE_ENV: '"production"',
ENV_CONFIG: '"dep"',
BASE_API: '"http://118.24.0.153:8080/admin"'
BASE_API: '"http://122.51.199.160:8080/admin"'
}
......@@ -20,6 +20,10 @@ import * as filters from './filters' // global filters
import permission from '@/directive/permission/index.js' // 权限判断指令
import Print from '@/utils/print' // 打印
Vue.use(Print)
Vue.use(Element, {
size: Cookies.get('size') || 'medium' // set element-ui default size
})
......
// 打印类属性、方法定义
/* eslint-disable */
const Print = function (dom, options) {
if (!(this instanceof Print)) return new Print(dom, options);
this.options = this.extend({
'noPrint': '.no-print'
}, options);
if ((typeof dom) === "string") {
this.dom = document.querySelector(dom);
} else {
this.isDOM(dom)
this.dom = this.isDOM(dom) ? dom : dom.$el;
}
this.init();
};
Print.prototype = {
init: function () {
var content = this.getStyle() + this.getHtml();
this.writeIframe(content);
},
extend: function (obj, obj2) {
for (var k in obj2) {
obj[k] = obj2[k];
}
return obj;
},
getStyle: function () {
var str = "",
styles = document.querySelectorAll('style,link');
for (var i = 0; i < styles.length; i++) {
str += styles[i].outerHTML;
}
str += "<style>" + (this.options.noPrint ? this.options.noPrint : '.no-print') + "{display:none;}</style>";
return str;
},
getHtml: function () {
var inputs = document.querySelectorAll('input');
var textareas = document.querySelectorAll('textarea');
var selects = document.querySelectorAll('select');
for (var k = 0; k < inputs.length; k++) {
if (inputs[k].type == "checkbox" || inputs[k].type == "radio") {
if (inputs[k].checked == true) {
inputs[k].setAttribute('checked', "checked")
} else {
inputs[k].removeAttribute('checked')
}
} else if (inputs[k].type == "text") {
inputs[k].setAttribute('value', inputs[k].value)
} else {
inputs[k].setAttribute('value', inputs[k].value)
}
}
for (var k2 = 0; k2 < textareas.length; k2++) {
if (textareas[k2].type == 'textarea') {
textareas[k2].innerHTML = textareas[k2].value
}
}
for (var k3 = 0; k3 < selects.length; k3++) {
if (selects[k3].type == 'select-one') {
var child = selects[k3].children;
for (var i in child) {
if (child[i].tagName == 'OPTION') {
if (child[i].selected == true) {
child[i].setAttribute('selected', "selected")
} else {
child[i].removeAttribute('selected')
}
}
}
}
}
return this.dom.outerHTML;
},
writeIframe: function (content) {
var w, doc, iframe = document.createElement('iframe'),
f = document.body.appendChild(iframe);
iframe.id = "myIframe";
//iframe.style = "position:absolute;width:0;height:0;top:-10px;left:-10px;";
iframe.setAttribute('style', 'position:absolute;width:0;height:0;top:-10px;left:-10px;');
w = f.contentWindow || f.contentDocument;
doc = f.contentDocument || f.contentWindow.document;
doc.open();
doc.write(content);
doc.close();
var _this = this
iframe.onload = function(){
_this.toPrint(w);
setTimeout(function () {
document.body.removeChild(iframe)
}, 100)
}
},
toPrint: function (frameWindow) {
try {
setTimeout(function () {
frameWindow.focus();
try {
if (!frameWindow.document.execCommand('print', false, null)) {
frameWindow.print();
}
} catch (e) {
frameWindow.print();
}
frameWindow.close();
}, 10);
} catch (err) {
console.log('err', err);
}
},
isDOM: (typeof HTMLElement === 'object') ?
function (obj) {
return obj instanceof HTMLElement;
} :
function (obj) {
return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string';
}
};
const MyPlugin = {}
MyPlugin.install = function (Vue, options) {
// 4. 添加实例方法
Vue.prototype.$print = Print
}
export default MyPlugin
......@@ -50,18 +50,21 @@ export default {
update() {
this.$refs['dataForm'].validate((valid) => {
if (!valid) {
return
return false
}
updateExpress(this.dataForm).then(response => {
this.$notify.success({
title: '成功',
message: '运费配置修改成功'
})
}).catch(response => {
this.$notify.error({
title: '失败',
message: response.data.errmsg
})
this.doUpdate()
})
},
doUpdate() {
updateExpress(this.dataForm).then(response => {
this.$notify.success({
title: '成功',
message: '运费配置修改成功'
})
}).catch(response => {
this.$notify.error({
title: '失败',
message: response.data.errmsg
})
})
}
......
......@@ -33,6 +33,20 @@ export default {
litemall_mall_address: '',
litemall_mall_phone: '',
litemall_mall_qq: ''
},
rules: {
litemall_mall_name: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
litemall_mall_address: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
litemall_mall_phone: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
litemall_mall_qq: [
{ required: true, message: '不能为空', trigger: 'blur' }
]
}
}
},
......@@ -49,6 +63,14 @@ export default {
this.init()
},
update() {
this.$refs['dataForm'].validate((valid) => {
if (!valid) {
return false
}
this.doUpdate()
})
},
doUpdate() {
updateMall(this.dataForm)
.then(response => {
this.$notify.success({
......
......@@ -34,7 +34,22 @@ export default {
name: 'ConfigOrder',
data() {
return {
dataForm: {}
dataForm: {
litemall_order_unpaid: 0,
litemall_order_unconfirm: 0,
litemall_order_comment: 0
},
rules: {
litemall_order_unpaid: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
litemall_order_unconfirm: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
litemall_order_comment: [
{ required: true, message: '不能为空', trigger: 'blur' }
]
}
}
},
created() {
......@@ -50,6 +65,14 @@ export default {
this.init()
},
update() {
this.$refs['dataForm'].validate((valid) => {
if (!valid) {
return false
}
this.doUpdate()
})
},
doUpdate() {
updateOrder(this.dataForm)
.then(response => {
this.$notify.success({
......
......@@ -5,9 +5,7 @@
:rules="rules"
:model="dataForm"
status-icon
label-width="300px"
>
label-width="300px">
<el-tabs tab-position="left" >
<el-tab-pane label="首页配置">
<el-form-item label="新品首发栏目商品显示数量" prop="litemall_wx_index_new">
......@@ -51,7 +49,35 @@ export default {
name: 'ConfigWx',
data() {
return {
dataForm: { }
dataForm: {
litemall_wx_index_new: 0,
litemall_wx_index_hot: 0,
litemall_wx_index_brand: 0,
litemall_wx_index_topic: 0,
litemall_wx_catlog_list: 0,
litemall_wx_catlog_goods: 0,
litemall_wx_share: false
},
rules: {
litemall_wx_index_new: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
litemall_wx_index_hot: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
litemall_wx_index_brand: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
litemall_wx_index_topic: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
litemall_wx_catlog_list: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
litemall_wx_catlog_goods: [
{ required: true, message: '不能为空', trigger: 'blur' }
]
}
}
},
created() {
......@@ -67,6 +93,14 @@ export default {
this.init()
},
update() {
this.$refs['dataForm'].validate((valid) => {
if (!valid) {
return false
}
this.doUpdate()
})
},
doUpdate() {
updateWx(this.dataForm)
.then(response => {
this.$notify.success({
......
......@@ -39,7 +39,7 @@
<template slot-scope="scope">
<el-button v-permission="['GET /admin/order/detail']" type="primary" size="mini" @click="handleDetail(scope.row)">详情</el-button>
<el-button v-permission="['POST /admin/order/ship']" v-if="scope.row.orderStatus==201" type="primary" size="mini" @click="handleShip(scope.row)">发货</el-button>
<el-button v-permission="['POST /admin/order/refund']" v-if="scope.row.orderStatus==202" type="primary" size="mini" @click="handleRefund(scope.row)">退款</el-button>
<el-button v-permission="['POST /admin/order/refund']" v-if="scope.row.orderStatus==202||scope.row.orderStatus==204" type="primary" size="mini" @click="handleRefund(scope.row)">退款</el-button>
</template>
</el-table-column>
</el-table>
......@@ -48,61 +48,66 @@
<!-- 订单详情对话框 -->
<el-dialog :visible.sync="orderDialogVisible" title="订单详情" width="800">
<el-form :data="orderDetail" label-position="left">
<el-form-item label="订单编号">
<span>{{ orderDetail.order.orderSn }}</span>
</el-form-item>
<el-form-item label="订单状态">
<el-tag>{{ orderDetail.order.orderStatus | orderStatusFilter }}</el-tag>
</el-form-item>
<el-form-item label="订单用户">
<span>{{ orderDetail.user.nickname }}</span>
</el-form-item>
<el-form-item label="用户留言">
<span>{{ orderDetail.order.message }}</span>
</el-form-item>
<el-form-item label="收货信息">
<span>(收货人){{ orderDetail.order.consignee }}</span>
<span>(手机号){{ orderDetail.order.mobile }}</span>
<span>(地址){{ orderDetail.order.address }}</span>
</el-form-item>
<el-form-item label="商品信息">
<el-table :data="orderDetail.orderGoods" border fit highlight-current-row>
<el-table-column align="center" label="商品名称" prop="goodsName" />
<el-table-column align="center" label="商品编号" prop="goodsSn" />
<el-table-column align="center" label="货品规格" prop="specifications" />
<el-table-column align="center" label="货品价格" prop="price" />
<el-table-column align="center" label="货品数量" prop="number" />
<el-table-column align="center" label="货品图片" prop="picUrl">
<template slot-scope="scope">
<img :src="scope.row.picUrl" width="40">
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item label="费用信息">
<span>
(实际费用){{ orderDetail.order.actualPrice }}元 =
(商品总价){{ orderDetail.order.goodsPrice }}元 +
(快递费用){{ orderDetail.order.freightPrice }}元 -
(优惠减免){{ orderDetail.order.couponPrice }}元 -
(积分减免){{ orderDetail.order.integralPrice }}元
</span>
</el-form-item>
<el-form-item label="支付信息">
<span>(支付渠道)微信支付</span>
<span>(支付时间){{ orderDetail.order.payTime }}</span>
</el-form-item>
<el-form-item label="快递信息">
<span>(快递公司){{ orderDetail.order.shipChannel }}</span>
<span>(快递单号){{ orderDetail.order.shipSn }}</span>
<span>(发货时间){{ orderDetail.order.shipTime }}</span>
</el-form-item>
<el-form-item label="收货信息">
<span>(确认收货时间){{ orderDetail.order.confirmTime }}</span>
</el-form-item>
</el-form>
<section ref="print">
<el-form :data="orderDetail" label-position="left">
<el-form-item label="订单编号">
<span>{{ orderDetail.order.orderSn }}</span>
</el-form-item>
<el-form-item label="订单状态">
<el-tag>{{ orderDetail.order.orderStatus | orderStatusFilter }}</el-tag>
</el-form-item>
<el-form-item label="订单用户">
<span>{{ orderDetail.user.nickname }}</span>
</el-form-item>
<el-form-item label="用户留言">
<span>{{ orderDetail.order.message }}</span>
</el-form-item>
<el-form-item label="收货信息">
<span>(收货人){{ orderDetail.order.consignee }}</span>
<span>(手机号){{ orderDetail.order.mobile }}</span>
<span>(地址){{ orderDetail.order.address }}</span>
</el-form-item>
<el-form-item label="商品信息">
<el-table :data="orderDetail.orderGoods" border fit highlight-current-row>
<el-table-column align="center" label="商品名称" prop="goodsName" />
<el-table-column align="center" label="商品编号" prop="goodsSn" />
<el-table-column align="center" label="货品规格" prop="specifications" />
<el-table-column align="center" label="货品价格" prop="price" />
<el-table-column align="center" label="货品数量" prop="number" />
<el-table-column align="center" label="货品图片" prop="picUrl">
<template slot-scope="scope">
<img :src="scope.row.picUrl" width="40">
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item label="费用信息">
<span>
(实际费用){{ orderDetail.order.actualPrice }}元 =
(商品总价){{ orderDetail.order.goodsPrice }}元 +
(快递费用){{ orderDetail.order.freightPrice }}元 -
(优惠减免){{ orderDetail.order.couponPrice }}元 -
(积分减免){{ orderDetail.order.integralPrice }}元
</span>
</el-form-item>
<el-form-item label="支付信息">
<span>(支付渠道)微信支付</span>
<span>(支付时间){{ orderDetail.order.payTime }}</span>
</el-form-item>
<el-form-item label="快递信息">
<span>(快递公司){{ orderDetail.order.shipChannel }}</span>
<span>(快递单号){{ orderDetail.order.shipSn }}</span>
<span>(发货时间){{ orderDetail.order.shipTime }}</span>
</el-form-item>
<el-form-item label="收货信息">
<span>(确认收货时间){{ orderDetail.order.confirmTime }}</span>
</el-form-item>
</el-form>
</section>
<span slot="footer" class="dialog-footer">
<el-button @click="orderDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="printOrder">打 印</el-button>
</span>
</el-dialog>
<!-- 发货对话框 -->
......@@ -138,7 +143,7 @@
</template>
<script>
import { listOrder, shipOrder, refundOrder, detailOrder } from '@/api/order'
import { detailOrder, listOrder, refundOrder, shipOrder } from '@/api/order'
import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
import checkPermission from '@/utils/permission' // 权限判断函数
......@@ -146,9 +151,11 @@ const statusMap = {
101: '未付款',
102: '用户取消',
103: '系统取消',
200: '已付款团购',
201: '已付款',
202: '申请退款',
203: '已退款',
204: '已超时团购',
301: '已发货',
401: '用户收货',
402: '系统收货'
......@@ -289,6 +296,10 @@ export default {
excel.export_json_to_excel2(tHeader, this.list, filterVal, '订单信息')
this.downloadLoading = false
})
},
printOrder() {
this.$print(this.$refs.print)
this.orderDialogVisible = false
}
}
}
......
......@@ -57,6 +57,27 @@
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources-vue</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes/static/vue</outputDirectory>
<resources>
<resource>
<directory>../litemall-vue/dist</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
......
......@@ -16,6 +16,12 @@
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
......
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