diff --git a/CHANGELOG.md b/CHANGELOG.md index fc6cc9c2..0e7b9ae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # 更新日志 +## 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*,支持移动端轻商城 diff --git a/README.md b/README.md index a90c9883..de1b0488 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/deploy/README.md b/deploy/README.md index 903619a2..ff337084 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -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 diff --git a/deploy/bin/reset.sh b/deploy/bin/reset.sh index d486e4a6..4ac1a1a2 100644 --- a/deploy/bin/reset.sh +++ b/deploy/bin/reset.sh @@ -14,7 +14,7 @@ PASSWORD= if test -z "$PASSWORD" then - echo "请设置云主机MySQL的root账号密码" + echo "请设置云服务器MySQL的root账号密码" exit 1 fi diff --git a/deploy/litemall/application.yml b/deploy/litemall/application.yml index 3bea1873..ac922ded 100644 --- a/deploy/litemall/application.yml +++ b/deploy/litemall/application.yml @@ -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 diff --git a/deploy/util/lazy.sh b/deploy/util/lazy.sh index fb7fadbd..a8eebdb0 100644 --- a/deploy/util/lazy.sh +++ b/deploy/util/lazy.sh @@ -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 diff --git a/doc/platform.md b/doc/platform.md index 6e4468f1..c699643a 100644 --- a/doc/platform.md +++ b/doc/platform.md @@ -1153,7 +1153,7 @@ public interface Storage { ## 2.4 litemall-all -在章节1.5中讨论的部署方案中设计了一种单主机单服务方案, +在章节1.5中讨论的部署方案中设计了一种单服务器单服务方案, 也就是说两个后台服务和静态文件都部署在一个Spring Boot可执行jar包中。 查看litemall-all模块,代码仅仅只有一个Application类。 diff --git a/doc/project.md b/doc/project.md index 12928e26..89edd997 100644 --- a/doc/project.md +++ b/doc/project.md @@ -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 - -http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html +这里可以安装openjdk-8-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-jre ``` -警告 -> "ppa:webupd8team/java" 不是Oracle官方PPA,可能存在安全隐患。 +如果希望采用jdk,而不是jre,则可以运行 + +```bash +sudo apt-get update +sudo apt-get install openjdk-8-jdk +``` + +注意 +> 如果用户想采用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部署方式比较简单不灵活,开发者可以参考开发自己的项目脚本。 diff --git a/doc/wxmall.md b/doc/wxmall.md index acb9821f..078aa442 100644 --- a/doc/wxmall.md +++ b/doc/wxmall.md @@ -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地址。 diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/OrderJob.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/OrderJob.java index 33ffe2b1..7ea803fb 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/OrderJob.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/job/OrderJob.java @@ -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; - - /** - * 自动取消订单 - *

- * 定时检查订单未付款情况,如果超时 LITEMALL_ORDER_UNPAID 分钟则自动取消订单 - * 定时时间是每次相隔半个小时。 - *

- * TODO - * 注意,因为是相隔半小时检查,因此导致订单真正超时时间是 [LITEMALL_ORDER_UNPAID, 30 + LITEMALL_ORDER_UNPAID] - */ - @Scheduled(fixedDelay = 30 * 60 * 1000) - @Transactional(rollbackFor = Exception.class) - public void checkOrderUnpaid() { - logger.info("系统开启任务检查订单是否已经超期自动取消订单"); - - List 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 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 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 grouponList = grouponService.queryJoinRecord(0); + for (LitemallGroupon groupon : grouponList) { + LitemallGrouponRules rules = rulesService.queryById(groupon.getRulesId()); + if (rulesService.isExpired(rules)) { + List 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 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("商品货品库存增加失败"); + } + } + } } diff --git a/litemall-admin/config/dep.env.js b/litemall-admin/config/dep.env.js index b64c6367..de1f18d5 100644 --- a/litemall-admin/config/dep.env.js +++ b/litemall-admin/config/dep.env.js @@ -1,5 +1,5 @@ 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"' } diff --git a/litemall-admin/src/main.js b/litemall-admin/src/main.js index de688815..155cfbfa 100644 --- a/litemall-admin/src/main.js +++ b/litemall-admin/src/main.js @@ -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 }) diff --git a/litemall-admin/src/utils/print.js b/litemall-admin/src/utils/print.js new file mode 100644 index 00000000..3f1dc822 --- /dev/null +++ b/litemall-admin/src/utils/print.js @@ -0,0 +1,135 @@ +// 打印类属性、方法定义 +/* 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 += ""; + + 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 diff --git a/litemall-admin/src/views/config/express.vue b/litemall-admin/src/views/config/express.vue index 6fc85237..5e3d59fa 100644 --- a/litemall-admin/src/views/config/express.vue +++ b/litemall-admin/src/views/config/express.vue @@ -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 }) }) } diff --git a/litemall-admin/src/views/config/mall.vue b/litemall-admin/src/views/config/mall.vue index f6c4fc52..d14abd6a 100644 --- a/litemall-admin/src/views/config/mall.vue +++ b/litemall-admin/src/views/config/mall.vue @@ -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({ diff --git a/litemall-admin/src/views/config/order.vue b/litemall-admin/src/views/config/order.vue index 329e11ec..2297a298 100644 --- a/litemall-admin/src/views/config/order.vue +++ b/litemall-admin/src/views/config/order.vue @@ -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({ diff --git a/litemall-admin/src/views/config/wx.vue b/litemall-admin/src/views/config/wx.vue index 70099e3a..ee755b5c 100644 --- a/litemall-admin/src/views/config/wx.vue +++ b/litemall-admin/src/views/config/wx.vue @@ -5,9 +5,7 @@ :rules="rules" :model="dataForm" status-icon - label-width="300px" - - > + label-width="300px"> @@ -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({ diff --git a/litemall-admin/src/views/mall/order.vue b/litemall-admin/src/views/mall/order.vue index b15a8248..d41ac483 100644 --- a/litemall-admin/src/views/mall/order.vue +++ b/litemall-admin/src/views/mall/order.vue @@ -39,7 +39,7 @@ @@ -48,61 +48,66 @@ - - - - {{ orderDetail.order.orderSn }} - - - {{ orderDetail.order.orderStatus | orderStatusFilter }} - - - {{ orderDetail.user.nickname }} - - - {{ orderDetail.order.message }} - - - (收货人){{ orderDetail.order.consignee }} - (手机号){{ orderDetail.order.mobile }} - (地址){{ orderDetail.order.address }} - - - - - - - - - - - - - - - - (实际费用){{ orderDetail.order.actualPrice }}元 = - (商品总价){{ orderDetail.order.goodsPrice }}元 + - (快递费用){{ orderDetail.order.freightPrice }}元 - - (优惠减免){{ orderDetail.order.couponPrice }}元 - - (积分减免){{ orderDetail.order.integralPrice }}元 - - - - (支付渠道)微信支付 - (支付时间){{ orderDetail.order.payTime }} - - - (快递公司){{ orderDetail.order.shipChannel }} - (快递单号){{ orderDetail.order.shipSn }} - (发货时间){{ orderDetail.order.shipTime }} - - - (确认收货时间){{ orderDetail.order.confirmTime }} - - +

+ + + {{ orderDetail.order.orderSn }} + + + {{ orderDetail.order.orderStatus | orderStatusFilter }} + + + {{ orderDetail.user.nickname }} + + + {{ orderDetail.order.message }} + + + (收货人){{ orderDetail.order.consignee }} + (手机号){{ orderDetail.order.mobile }} + (地址){{ orderDetail.order.address }} + + + + + + + + + + + + + + + + (实际费用){{ orderDetail.order.actualPrice }}元 = + (商品总价){{ orderDetail.order.goodsPrice }}元 + + (快递费用){{ orderDetail.order.freightPrice }}元 - + (优惠减免){{ orderDetail.order.couponPrice }}元 - + (积分减免){{ orderDetail.order.integralPrice }}元 + + + + (支付渠道)微信支付 + (支付时间){{ orderDetail.order.payTime }} + + + (快递公司){{ orderDetail.order.shipChannel }} + (快递单号){{ orderDetail.order.shipSn }} + (发货时间){{ orderDetail.order.shipTime }} + + + (确认收货时间){{ orderDetail.order.confirmTime }} + + +
+ + 取 消 + 打 印 + @@ -138,7 +143,7 @@