[TOC]
作者:[email protected] (瑞新)
看实现类:alt+commed+b
-
页面
- 前端:vue——webapp
- 后端:springboot——bootstrap+freemarker+jquery
-
前端后端接口:restful
-
数据库springboot+jpa
- springboot
- mybits
-
缓存springboot+redis
- 分布式session
- 分布式锁
-
消息推送
- websocket
扫码登录
模版消息推送
微信支付/退款
git clone https://git.imooc.com/coding-117/coding-117.git
切换标签分支
git checkout -b xxx xxx
查看当前分支
git log
返回刚刚已切换的标签
git checkout -b xxx
终端插件
Zsh autosuggestions
Idea2019
Java -version1.8.0.111
mvn -version3.9
Springboot2.2.2
centos7.3
mysql5.7
Nginx1.11.7
redis3.2.8
推荐:meaven镜像仓库
vim ~/.m2/settings.xml
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
<!--配置profiles节点-->
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<jdk>1.8</jdk>
</activation>
<repositories>
<repository>
<id>nexus</id>
<name>local private nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
先学习以下前置知识。如果还不了解,点击对应的实战内容,移步学习!记得star哦~
meaven基础
springboot基础
GitHub:点击预览——极客浪漫红包表白程序
详细文档:点击搜索——极客浪漫红包表白详解
GitHub:点击预览——基因芯片个人信息程序
详细文档:点击搜索——基因芯片个人信息程序
javaweb基础
GitHub:点击预览——SpringBoot进阶基因芯片个人信息程序
详细文档:点击搜索——SpringBoot进阶基因芯片个人信息程序
-
角色划分
- 买家(手机端:微信公众账号提供服务)
- 卖家(pc端)
-
功能模块划分
-
商品
- 商品列表
-
订单
买家
- 订单创建
- 订单查询
- 订单取消……
卖家
- 商品管理
- 订单管理
- 类目管理……
-
类目
-
-
部署架构
- 卖家、买家请求服务器:NGINX在虚拟机中搭建好了
- 前端资源获取:NGINX服务器
- 后端资源获取:NGINX服务器-》tomcat(分布式) -〉做了缓存到redies,没做缓存到mysql
-
数据库设计
- 类目表(product_category)
- 商品表(product_info)
- 订单详情(order_detil)
- 订单主表(order_master)
- 卖家信息表(seller_info)
Mysql5.7,若5.6default current_timestamp有两个会报错
-- 商品表
create table `product_info`(
`product_id` varchar(32) not null comment '商品id',
`product_name` varchar(64) not null comment '商品名称',
`product_price` decimal(8,2) not null comment '商品单价',
`product_stock` int not null comment '商品库存',
`product_description` varchar(64) comment '商品描述',
`product_icon` varchar(512) comment '商品小图',
`category_type` int not null comment '类目编号',
`product_status` tinyint(3) DEFAULT '0' COMMENT '商品状态,0正常1下架',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '更新时间',
primary key (`product_id`)
)comment '商品表';
-- 类目表
create table `product_category`(
`category_id` int not null auto_increment comment '类目id',
`category_name` varchar(64) not null comment '类目名称',
`category_type` int not null comment '类目编号',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '更新时间',
primary key (`category_id`),
unique key `uqe_category_type` (`category_type`)
) comment '类目表';
-- 订单表
create table `order_master`(
`order_id` varchar(32) not null comment '订单id',
`buyer_name` varchar(32) not null comment '买家名字',
`buyer_phone` varchar(32) not null comment '买家电话',
`buyer_address` varchar(128) not null comment '买家地址',
`buyer_openid` varchar(64) not null comment '买家微信openid',
`order_amount` decimal(8,2) not null comment '订单总金额',
`order_status` tinyint(3) not null default '0' comment '订单状态,默认0新下单',
`pay_status` tinyint(3) not null default '0' comment '支付状态,默认0未支付',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '更新时间',
primary key (`order_id`),
key `idx_buyer_openid` (`buyer_openid`)
) comment '订单表';
-- 订单详情表
create table `order_detail`(
`detail_id` varchar(32) not null comment '订单详情id',
`order_id` varchar(32) not null comment '订单id',
`product_id` varchar(32) not null comment '商品id',
`product_name` varchar(64) not null comment '商品名称',
`product_price` decimal(8,2) not null comment '商品价格',
`product_quantity` int not null comment '商品数量',
` ` varchar(512) not null comment '商品小图',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '更新时间',
primary key (`detail_id`),
key `idx_order_id` (`order_id`)
) comment '订单详情表';
-- 卖家(登录后台使用, 卖家登录之后可能直接采用微信扫码登录,不使用账号密码)
create table `seller_info` (
`id` varchar(32) not null,
`username` varchar(32) not null,
`password` varchar(32) not null,
`openid` varchar(64) not null comment '微信openid',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`id`)
) comment '卖家信息表';
运行虚拟机:保证虚拟机和主机相互ping通
联通虚拟机中的数据库:创建数据库选择utf-8mb4(存微信聊天的表情)
虚拟机配置好vue+mysql+redis环境,本次只专注后端开发。
ifconfig
我的虚拟机端口号192.168.210.132
访问端口号测试微信登录后台。
VirtualBox-5.1.22
虚拟机系统 centos7.3
账号 root
密码 123456
- jdk 1.8.0_111
- nginx 1.11.7
- mysql 5.7.17
- redis 3.2.8
jdk
- 路径 /usr/local/jdk1.8.0_111
nginx
- 路径 /usr/local/nginx
- 启动 nginx
- 重启 nginx -s reload
mysql
- 配置 /etc/my.conf
- 账号 root
- 密码 123456
- 端口 3306
- 启动 systemctl start mysqld
- 停止 systemctl stop mysqld
redis
- 路径 /usr/local/redis
- 配置 /etc/reis.conf
- 端口 6379
- 密码 123456
- 启动 systemctl start redis
- 停止 systemctl stop redis
tomcat
- 路径 /usr/local/tomcat
- 启动 systemctl start tomcat
- 停止 systemctl stop tomcat
Jdk 1.8
Maven 3.3.9
使用阿里云的镜像地址会快点~ vim .m2/settings.xml
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
开发idea
能够实现日志输出的工具包,描述运行状态的所有时间。
例如:用户下线,接口超时,数据库崩溃
定制输出目标
定制输出格式
携带上下文信息
运行时选择性输出
灵活配置-运维
优秀性能
jdk自带logging jul
apach自带clogging jcl
log4j
log4j2
logback
slf4j
hibinate的jboss-logging
日志门面 | 日志实现 |
---|---|
JCL | Log4j(作者ceki,太烂了作者不想改) |
SLF4j(作者ceki ) | Log4j2(apach借名,优于Logback性能, 因为太先进,所以坑多) |
Jobs-logging(诞生就不是为服务大众,不受亲睐 ) | Logback(作者ceki) |
Jul(虽然官方,但实现简陋,多方面受到开发者吐槽) |
推荐:日志门面SLF4j, 日志实现logback
传入包名创建日志工厂
引入lambol的@Slf4j直接log.
打印变量
package com.bennyrhys.wechat_order;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @Author bennyrhys
* @Date 2020-06-24 22:12
*/
@SpringBootTest
@Slf4j
public class LoggerTest {
/**
* 指定当前输出所对应的类
* 每次这样写麻烦,用注解代替
*
* 引入@slf4j 包名lambok @Data也是lambok,pom先引入lombok小插件 log代替logger
* 不仅在类的get\set方法带来很大便捷,日志使用也很便捷
*/
// private final Logger logger = LoggerFactory.getLogger(LoggerTest.class);
/**
* 测试-日志
* 快捷键:
* 看级别comment+n,搜索level找slf4j
* ERROR(40, "ERROR"),
* WARN(30, "WARN"),
* INFO(20, "INFO"),
* DEBUG(10, "DEBUG"),
* TRACE(0, "TRACE");
*/
@Test
public void test1(){
// logger.debug("debug...");
// logger.info("info...");
// logger.error("error...");
// 引入@Slf4j后
log.debug("debug...");
log.info("info...");
log.error("error...");
// 引入变量 【建议使用{}占位符】
String name = "bennyrhys";
String tell = "158";
log.info("name:" + name + ", tell:" + tell);
log.info("name:{}, tell:{}", name, tell);
}
}
pom.xml
<!-- 添加小工具 日志方便,get、set方便-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
application.yml只能简单配置(日志路径、格式)
logbak-spring.xml可以复杂配置(区分info和error日志,每天一个日志)
注意:可以通过springframeworkjar包org-logging-logback下默认配置引用信息
application.yml
# 日志配置
logging:
pattern:
console: "%d - %msg%n"
# level:
# com.bennyrhys.wechat_order.LoggerTest: debug
# level: debug # 直接指定级别
# path: /Users/bennyrhys/Documents/Idea_Demo/WeChat-Order/wechat_order/src/main/resources/ # 弃用了
# file: /Users/bennyrhys/Documents/Idea_Demo/WeChat-Order/wechat_order/src/main/resources/sell.log # 弃用了
logback-spring.xml
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- 控制台输出-->
<appender name = "consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%d - %msg%n
</pattern>
</layout>
</appender>
<!-- 文件info -->
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤级别 只保留,条件等级比对过滤
public enum FilterReply {
DENY, //禁止
NEUTRAL, // 忽略
ACCEPT; // 接受
-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>
%msg%n
</pattern>
</encoder>
<!-- 滚动策略 每天输出一个文件+路径-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 路径-->
<fileNamePattern>/Users/bennyrhys/Documents/Idea_Demo/WeChat-Order/wechat_order/src/main/resources/info.%d.log</fileNamePattern>
</rollingPolicy>
</appender>
<!-- 文件error -->
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤级别 只保留范围:比当前范围大-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>
%msg%n
</pattern>
</encoder>
<!-- 滚动策略 每天输出一个文件+路径-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 路径-->
<fileNamePattern>/Users/bennyrhys/Documents/Idea_Demo/WeChat-Order/wechat_order/src/main/resources/error.%d.log</fileNamePattern>
</rollingPolicy>
</appender>
<!--级别-->
<root level="INFO">
<appender-ref ref="consoleLog" />
<appender-ref ref="fileInfoLog" />
<appender-ref ref="fileErrorLog" />
</root>
</configuration>
###商品列表
GET /sell/buyer/product/list
参数
无
返回
{
"code": 0,
"msg": "成功",
"data": [
{
"name": "热榜",
"type": 1,
"foods": [
{
"id": "123456",
"name": "皮蛋粥",
"price": 1.2,
"description": "好吃的皮蛋粥",
"icon": "http://xxx.com",
}
]
},
{
"name": "好吃的",
"type": 2,
"foods": [
{
"id": "123457",
"name": "慕斯蛋糕",
"price": 10.9,
"description": "美味爽口",
"icon": "http://xxx.com",
}
]
}
]
}
###创建订单
POST /sell/buyer/order/create
参数
name: "张三"
phone: "18868822111"
address: "慕课网总部"
openid: "ew3euwhd7sjw9diwkq" //用户的微信openid
items: [{
productId: "1423113435324",
productQuantity: 2 //购买数量
}]
返回
{
"code": 0,
"msg": "成功",
"data": {
"orderId": "147283992738221"
}
}
###订单列表
GET /sell/buyer/order/list
参数
openid: 18eu2jwk2kse3r42e2e
page: 0 //从第0页开始
size: 10
返回
{
"code": 0,
"msg": "成功",
"data": [
{
"orderId": "161873371171128075",
"buyerName": "张三",
"buyerPhone": "18868877111",
"buyerAddress": "慕课网总部",
"buyerOpenid": "18eu2jwk2kse3r42e2e",
"orderAmount": 0,
"orderStatus": 0,
"payStatus": 0,
"createTime": 1490171219,
"updateTime": 1490171219,
"orderDetailList": null
},
{
"orderId": "161873371171128076",
"buyerName": "张三",
"buyerPhone": "18868877111",
"buyerAddress": "慕课网总部",
"buyerOpenid": "18eu2jwk2kse3r42e2e",
"orderAmount": 0,
"orderStatus": 0,
"payStatus": 0,
"createTime": 1490171219,
"updateTime": 1490171219,
"orderDetailList": null
}]
}
###查询订单详情
GET /sell/buyer/order/detail
参数
openid: 18eu2jwk2kse3r42e2e
orderId: 161899085773669363
返回
{
"code": 0,
"msg": "成功",
"data": {
"orderId": "161899085773669363",
"buyerName": "李四",
"buyerPhone": "18868877111",
"buyerAddress": "慕课网总部",
"buyerOpenid": "18eu2jwk2kse3r42e2e",
"orderAmount": 18,
"orderStatus": 0,
"payStatus": 0,
"createTime": 1490177352,
"updateTime": 1490177352,
"orderDetailList": [
{
"detailId": "161899085974995851",
"orderId": "161899085773669363",
"productId": "157875196362360019",
"productName": "招牌奶茶",
"productPrice": 9,
"productQuantity": 2,
"productIcon": "http://xxx.com",
"productImage": "http://xxx.com"
}
]
}
}
###取消订单
POST /sell/buyer/order/cancel
参数
openid: 18eu2jwk2kse3r42e2e
orderId: 161899085773669363
返回
{
"code": 0,
"msg": "成功",
"data": null
}
###获取openid
重定向到 /sell/wechat/authorize
参数
returnUrl: http://xxx.com/abc //【必填】
返回
http://xxx.com/abc?openid=oZxSYw5ldcxv6H0EU67GgSXOUrVg
###商品列表
GET /sell/buyer/product/list
参数
无
返回
{
"code": 0,
"msg": "成功",
"data": [
{
"name": "热榜",
"type": 1,
"foods": [
{
"id": "123456",
"name": "皮蛋粥",
"price": 1.2,
"description": "好吃的皮蛋粥",
"icon": "http://xxx.com",
}
]
},
{
"name": "好吃的",
"type": 2,
"foods": [
{
"id": "123457",
"name": "慕斯蛋糕",
"price": 10.9,
"description": "美味爽口",
"icon": "http://xxx.com",
}
]
}
]
}
###创建订单
POST /sell/buyer/order/create
参数
name: "张三"
phone: "18868822111"
address: "慕课网总部"
openid: "ew3euwhd7sjw9diwkq" //用户的微信openid
items: [{
productId: "1423113435324",
productQuantity: 2 //购买数量
}]
返回
{
"code": 0,
"msg": "成功",
"data": {
"orderId": "147283992738221"
}
}
###订单列表
GET /sell/buyer/order/list
参数
openid: 18eu2jwk2kse3r42e2e
page: 0 //从第0页开始
size: 10
返回
{
"code": 0,
"msg": "成功",
"data": [
{
"orderId": "161873371171128075",
"buyerName": "张三",
"buyerPhone": "18868877111",
"buyerAddress": "慕课网总部",
"buyerOpenid": "18eu2jwk2kse3r42e2e",
"orderAmount": 0,
"orderStatus": 0,
"payStatus": 0,
"createTime": 1490171219,
"updateTime": 1490171219,
"orderDetailList": null
},
{
"orderId": "161873371171128076",
"buyerName": "张三",
"buyerPhone": "18868877111",
"buyerAddress": "慕课网总部",
"buyerOpenid": "18eu2jwk2kse3r42e2e",
"orderAmount": 0,
"orderStatus": 0,
"payStatus": 0,
"createTime": 1490171219,
"updateTime": 1490171219,
"orderDetailList": null
}]
}
###查询订单详情
GET /sell/buyer/order/detail
参数
openid: 18eu2jwk2kse3r42e2e
orderId: 161899085773669363
返回
{
"code": 0,
"msg": "成功",
"data": {
"orderId": "161899085773669363",
"buyerName": "李四",
"buyerPhone": "18868877111",
"buyerAddress": "慕课网总部",
"buyerOpenid": "18eu2jwk2kse3r42e2e",
"orderAmount": 18,
"orderStatus": 0,
"payStatus": 0,
"createTime": 1490177352,
"updateTime": 1490177352,
"orderDetailList": [
{
"detailId": "161899085974995851",
"orderId": "161899085773669363",
"productId": "157875196362360019",
"productName": "招牌奶茶",
"productPrice": 9,
"productQuantity": 2,
"productIcon": "http://xxx.com",
"productImage": "http://xxx.com"
}
]
}
}
###取消订单
POST /sell/buyer/order/cancel
参数
openid: 18eu2jwk2kse3r42e2e
orderId: 161899085773669363
返回
{
"code": 0,
"msg": "成功",
"data": null
}
###获取openid
重定向到 /sell/wechat/authorize
参数
returnUrl: http://xxx.com/abc //【必填】
返回
http://xxx.com/abc?openid=oZxSYw5ldcxv6H0EU67GgSXOUrVg
###支付订单
重定向 /sell/pay/create
参数
orderId: 161899085773669363
returnUrl: http://xxx.com/abc/order/161899085773669363
返回
http://xxx.com/abc/order/161899085773669363
断言
Assert -> Assertions
jpa
findOne->findById返回值Optional对象,
暂时解决抛红.get()。之前查不到返回null但是.get()会抛异常
.orElse(null),指定查不到返回null
配置不能用驼峰
projectUrl
Project-url
projrctService
findOne()可以添加图片到又拍云。
写法视频13.42
数据库链接
Cj.
Server.ontext-path -> server.servlet.context-path:/sell
为什么多servlet因为2.0多了webflux特性
警告废弃
@NotEmpty,进入注解更换包
new pagerequest()-> pagerequest().of
URLEncode.encode
增删改查
时间自动更新
事务回滚
根据jpa规则写接口方法
pom.xml
<!-- 添加小工具 日志方便,get、set方便【idea中下载插件lombok 】-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 引入数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- jpa操作数据库-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
application.yml
# 数据库配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://192.168.210.132/db_wxds?characterEncoding=utf-8&userSSL=false
jpa:
show-sql: true
属性ProductCategory
package com.bennyrhys.wechat_order.daoobject;
import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* 类目
* 注意表名:idea中驼峰,sql表是_。springDataJpa做的映射。(如果非要自定义idea的表名,下边@Table,不建议)
* @Author bennyrhys
* @Date 2020-06-26 10:28
*/
//@Table(name = "s_my_table")
// 数据库映射成对象
@Entity
// 自动更新时间
@DynamicUpdate
// 每次属性类型改变不想重写get、set方法生成麻烦。那就全部去掉@Data代替【插件lombok,不会影响性能,打包时自动添加get、set】
@Data
public class ProductCategory {
// 字段命名也是对应_为驼峰
// 类目id【主键 自增指定生成策略】
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer categoryId;
// 类目名称
private String categoryName;
// 类目编号
private Integer categoryType;
//// 创建时间
// private Date createTime;
//// 更新时间
// private Date updateTime;
// get、set toString
}
Dao-ProductCategoryRepository
package com.bennyrhys.wechat_order.repository;
import com.bennyrhys.wechat_order.daoobject.ProductCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* @Author bennyrhys
* @Date 2020-06-26 10:40
*/
// 继承jpa<对象, 主键类型>
// 直接通过接口名创建测试类
public interface ProductCategoryRepository extends JpaRepository<ProductCategory, Integer> {
// 注意需要按照指定格式
// 通过类目编号数组查询类目
List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList);
}
测试ProductCategoryRepositoryTest
package com.bennyrhys.wechat_order.repository;
import com.bennyrhys.wechat_order.daoobject.ProductCategory;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Assert;
import javax.transaction.Transactional;
import java.util.Arrays;
import java.util.List;
/**
* @Author bennyrhys
* @Date 2020-06-26 10:42
*/
@SpringBootTest
public class ProductCategoryRepositoryTest {
@Autowired
private ProductCategoryRepository repository;
// 查
@Test
public void findById(){
ProductCategory productCategory = repository.findById(2).orElse(null);
System.out.println(productCategory.toString());
}
// 增
// 为保证数据库干净 操作完就回滚【@Transactional完全回滚。不像事务里,失败才回滚】
@Test
@Transactional
public void saveOne() {
ProductCategory productCategory = new ProductCategory();
productCategory.setCategoryName("女生最爱");
productCategory.setCategoryType(2);
ProductCategory result = repository.save(productCategory);
// 断言
if (result == null){
Assert.isTrue(false,"null才抛异常");
}
}
// 更,共用save但是要指定主键
@Test
public void updateOne() {
ProductCategory productCategory = new ProductCategory();
productCategory.setCategoryId(2);
productCategory.setCategoryName("女生最爱");
productCategory.setCategoryType(2);
repository.save(productCategory);
}
// 场景模拟-先查出后修改
@Test
public void findAndSave(){
ProductCategory productCategory = repository.findById(2).orElse(null);
productCategory.setCategoryType(2);
repository.save(productCategory);
}
// 接口制定方法:类目编号数组查类目
@Test
public void findByCategoryTypeIn() {
List<Integer> list = Arrays.asList(1,2,4);
List<ProductCategory> result = repository.findByCategoryTypeIn(list);
// 断言不为0则成功
Assert.notEmpty(result,"不为空数组");
}
}
com.bennyrhys.wechat_order.service.CategoryService
package com.bennyrhys.wechat_order.service;
import com.bennyrhys.wechat_order.daoobject.ProductCategory;
import java.util.List;
/**
* 类目
* @Author bennyrhys
* @Date 2020-06-26 16:12
*/
public interface CategoryService {
ProductCategory findOne(Integer categoryId);
List<ProductCategory> findAll();
List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList);
ProductCategory save(ProductCategory productCategory);
}
CategorySericeImpl
package com.bennyrhys.wechat_order.service.impl;
import com.bennyrhys.wechat_order.daoobject.ProductCategory;
import com.bennyrhys.wechat_order.repository.ProductCategoryRepository;
import com.bennyrhys.wechat_order.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 类目
* @Author bennyrhys
* @Date 2020-06-26 16:16
*/
@Service
public class CategorySericeImpl implements CategoryService {
@Autowired
ProductCategoryRepository repository;
@Override
public ProductCategory findOne(Integer categoryId) {
return repository.findById(categoryId).orElse(null);
}
@Override
public List<ProductCategory> findAll() {
return repository.findAll();
}
@Override
public List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList) {
return repository.findByCategoryTypeIn(categoryTypeList);
}
@Override
public ProductCategory save(ProductCategory productCategory) {
return repository.save(productCategory);
}
}
CategorySericeImplTest
package com.bennyrhys.wechat_order.service.impl;
import com.bennyrhys.wechat_order.daoobject.ProductCategory;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* @Author bennyrhys
* @Date 2020-06-27 08:46
*/
@SpringBootTest
class CategorySericeImplTest {
@Autowired
CategorySericeImpl categorySerice;
@Test
void findOne() {
ProductCategory one = categorySerice.findOne(1);
Assertions.assertEquals(1, one.getCategoryId());
}
@Test
void findAll() {
List<ProductCategory> categoryList = categorySerice.findAll();
Assertions.assertNotEquals(0,categoryList.size());
}
@Test
void findByCategoryTypeIn() {
List<ProductCategory> byCategoryTypeIn = categorySerice.findByCategoryTypeIn(Arrays.asList(1, 2, 3, 4, 5, 6, 7));
Assertions.assertNotEquals(0, byCategoryTypeIn.size());
}
@Test
void save() {
ProductCategory category = new ProductCategory("男生专享", 3);
ProductCategory result = categorySerice.save(category);
Assertions.assertNotNull(result);
}
}
package com.bennyrhys.wechat_order.enums;
import lombok.Getter;
/**
* 商品状态
* @Author bennyrhys
* @Date 2020-06-27 17:00
*/
@Getter
public enum ProductStatusEnum {
UP(0, "在架"),
DOWN (1, "下架");
private Integer code;
private String msg;
ProductStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
ProductInfo
package com.bennyrhys.wechat_order.daoobject;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.math.BigDecimal;
/**
* 商品
* @Author bennyrhys
* @Date 2020-06-27 09:34
*/
@Entity
@Data
public class ProductInfo {
@Id
// 商品id
private String productId;
// 商品名称
private String productName;
// 商品单价
private BigDecimal productPrice;
// 商品库存
private Integer productStock;
// 商品描述
private String productDescription;
// 商品小图
private String productIcon;
// 类目编号
private Integer categoryType;
// 商品状态,0正常1下架
private Integer productStatus;
}
ProductInfoRepository
package com.bennyrhys.wechat_order.repository;
import com.bennyrhys.wechat_order.daoobject.ProductInfo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* @Author bennyrhys
* @Date 2020-06-27 09:43
*/
public interface ProductInfoRepository extends JpaRepository<ProductInfo, String> {
/**
* 查询上架商品
*/
List<ProductInfo> findByProductStatus(Integer productStatus);
}
ProductInfoRepositoryTest
package com.bennyrhys.wechat_order.repository;
import com.bennyrhys.wechat_order.daoobject.ProductInfo;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.math.BigDecimal;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* @Author bennyrhys
* @Date 2020-06-27 09:50
*/
@SpringBootTest
class ProductInfoRepositoryTest {
@Autowired
ProductInfoRepository repository;
@Test
public void saveTest() {
ProductInfo productInfo = new ProductInfo();
productInfo.setProductId("11");
productInfo.setCategoryType(1);
productInfo.setProductName("衬衫");
productInfo.setProductDescription("纯棉,超薄");
productInfo.setProductIcon("http://xxxx.png");
productInfo.setProductPrice(new BigDecimal(9.9));
productInfo.setProductStock(100);
productInfo.setProductStatus(0);
ProductInfo result = repository.save(productInfo);
Assertions.assertNotNull(result);
}
// 测试上架商品
@Test
public void findByProductStatus(){
List<ProductInfo> productInfoList = repository.findByProductStatus(0);
Assertions.assertNotEquals(0, productInfoList.size());
}
}
ProductSerice
package com.bennyrhys.wechat_order.service;
import com.bennyrhys.wechat_order.daoobject.ProductInfo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
/**
* 商品
* @Author bennyrhys
* @Date 2020-06-27 16:49
*/
public interface ProductSerice {
ProductInfo findOne(String productId);
/**
* 查询在架的商品-c端
* @return
*/
List<ProductInfo> findUpAll();
/**
* 查找全部商品
* @param pageable 分页 springframe domain的包
* @return
*/
Page<ProductInfo> findAll(Pageable pageable);
ProductInfo save(ProductInfo productInfo);
// 加库存 减库存
}
ProductServiceImpl
package com.bennyrhys.wechat_order.service.impl;
import com.bennyrhys.wechat_order.daoobject.ProductInfo;
import com.bennyrhys.wechat_order.enums.ProductStatusEnum;
import com.bennyrhys.wechat_order.repository.ProductInfoRepository;
import com.bennyrhys.wechat_order.service.ProductSerice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 商品
* @Author bennyrhys
* @Date 2020-06-27 16:55
*/
@Service
public class ProductServiceImpl implements ProductSerice {
@Autowired
ProductInfoRepository repository;
@Override
public ProductInfo findOne(String productId) {
return repository.findById(productId).orElse(null);
}
/**
* 查询在架的商品-c端
*
* @return
*/
@Override
public List<ProductInfo> findUpAll() {
return repository.findByProductStatus(ProductStatusEnum.UP.getCode());
}
/**
* 查找全部商品
*
* @param pageable 分页 springframe domain的包
* @return
*/
@Override
public Page<ProductInfo> findAll(Pageable pageable) {
return repository.findAll(pageable);
}
@Override
public ProductInfo save(ProductInfo productInfo) {
return repository.save(productInfo);
}
}
测试
package com.bennyrhys.wechat_order.service.impl;
import com.bennyrhys.wechat_order.daoobject.ProductInfo;
import com.bennyrhys.wechat_order.enums.ProductStatusEnum;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import java.math.BigDecimal;
import java.util.List;
/**
* 商品
* @Author bennyrhys
* @Date 2020-06-27 17:05
*/
@SpringBootTest
class ProductServiceImplTest {
@Autowired
private ProductServiceImpl productSerice;
@Test
void findOne() {
ProductInfo productInfo = productSerice.findOne("11");
Assertions.assertEquals("11", productInfo.getProductId());
}
@Test
void findUpAll() {
List<ProductInfo> productInfoList = productSerice.findUpAll();
Assertions.assertNotEquals(0, productInfoList.size());
}
@Test
void findAll() {
// 配置PageAble对象
PageRequest pageRequest = PageRequest.of(0, 2);
Page<ProductInfo> productInfos = productSerice.findAll(pageRequest);
// System.out.println(productInfos.getTotalElements());
Assertions.assertNotEquals(0, productInfos.getTotalElements());
}
@Test
void save() {
ProductInfo productInfo = new ProductInfo();
productInfo.setProductId("22");
productInfo.setCategoryType(1);
productInfo.setProductName("皮蛋瘦肉粥");
productInfo.setProductDescription("超甜,很好喝");
productInfo.setProductIcon("http://xxxx.png");
productInfo.setProductPrice(new BigDecimal(5.9));
productInfo.setProductStock(100);
productInfo.setProductStatus(ProductStatusEnum.DOWN.getCode());
ProductInfo result = productSerice.save(productInfo);
Assertions.assertNotNull(result);
}
}
商品列表
com.bennyrhys.wechat_order.VO.ResultVO
package com.bennyrhys.wechat_order.VO;
import lombok.Data;
/**
* http请求返回的最外层对象
* @Author bennyrhys
* @Date 2020-06-27 19:56
*/
@Data
public class ResultVO<T> {
// 错误码
private Integer code;
// 提示
private String msg;
// 具体内容
private T data;
}
package com.bennyrhys.wechat_order.VO;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* 商品类目
* @Author bennyrhys
* @Date 2020-06-27 20:22
*/
@Data
public class ProductVO {
@JsonProperty("name")
private String productName;
@JsonProperty("type")
private Integer productType;
@JsonProperty("foods")
private List<ProductInfoVO> productInfoVOList;
}
package com.bennyrhys.wechat_order.VO;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* 商品详情(删减脱敏 )
* @Author bennyrhys
* @Date 2020-06-27 20:24
*/
@Data
public class ProductInfoVO {
@JsonProperty("id")
private String productId;
@JsonProperty("name")
private String productName;
@JsonProperty("price")
private BigDecimal productPrice;
@JsonProperty("description")
private String productDescription;
@JsonProperty("icon")
private String productIcon;
}
封装状态
com.bennyrhys.wechat_order.utils.ResultVOUtils
package com.bennyrhys.wechat_order.utils;
import com.bennyrhys.wechat_order.VO.ResultVO;
/**
* 通用返回消息
* @Author bennyrhys
* @Date 2020-06-27 21:24
*/
public class ResultVOUtils {
public static ResultVO success(Object object) {
ResultVO<Object> resultVO = new ResultVO<>();
resultVO.setData(object);
resultVO.setCode(0);
resultVO.setMsg("成功");
return resultVO;
}
public static ResultVO success() {
return success(null);
}
public static ResultVO error(Integer code, String msg) {
ResultVO<Object> resultVO = new ResultVO<>();
resultVO.setCode(code);
resultVO.setMsg(msg);
return resultVO;
}
}
控制台
package com.bennyrhys.wechat_order.controller;
import com.bennyrhys.wechat_order.VO.ProductInfoVO;
import com.bennyrhys.wechat_order.VO.ProductVO;
import com.bennyrhys.wechat_order.VO.ResultVO;
import com.bennyrhys.wechat_order.daoobject.ProductCategory;
import com.bennyrhys.wechat_order.daoobject.ProductInfo;
import com.bennyrhys.wechat_order.service.CategoryService;
import com.bennyrhys.wechat_order.service.ProductSerice;
import com.bennyrhys.wechat_order.utils.ResultVOUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 买家商品
* @Author bennyrhys
* @Date 2020-06-27 19:30
*/
@RestController
@RequestMapping("/buyer/product")
public class BuyerProductController {
@Autowired
private ProductSerice productSerice;
@Autowired
private CategoryService categoryService;
@GetMapping("list")
public ResultVO list() {
// 1. 查询所有上架商品
List<ProductInfo> productInfoList = productSerice.findUpAll();
// 2. 查询类目(一次查清)
// List<Integer> categoryTypeList = new ArrayList<>();
// 获取categoryType-传统方法
// for (ProductInfo productInfo : productInfoList) {
// categoryTypeList.add(productInfo.getCategoryType());
// }
// 精简方法(java8 lambda)
List<Integer> categoryTypeList = productInfoList.stream()
.map(e -> e.getCategoryType())
.collect(Collectors.toList());
List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypeList);
// 3. 数据拼装
List<ProductVO> productVOList = new ArrayList<>();
for (ProductCategory productCategory : productCategoryList) {
ProductVO productVO = new ProductVO();
productVO.setProductType(productCategory.getCategoryType());
productVO.setProductName(productCategory.getCategoryName());
List<ProductInfoVO> productInfoVOList = new ArrayList<>();
for (ProductInfo productInfo : productInfoList) {
if (productInfo.getCategoryType().equals(productCategory.getCategoryType())){
ProductInfoVO productInfoVO = new ProductInfoVO();
// 每个重新赋值太慢 对象拷贝
BeanUtils.copyProperties(productInfo, productInfoVO);
productInfoVOList.add(productInfoVO);
}
}
productVO.setProductInfoVOList(productInfoVOList);
productVOList.add(productVO);
}
// 【直接返回】
// ResultVO<Object> resultVO = new ResultVO<>();
// resultVO.setData(productVOList);
// resultVO.setCode(0);
// resultVO.setMsg("成功");
// 【测试 json 结构】
// ResultVO<Object> vo = new ResultVO<>();
// ProductVO productVO = new ProductVO();
// ProductInfoVO productInfoVO = new ProductInfoVO();
//
// vo.setCode(0);
// vo.setMsg("成功");
//
// productVO.setProductInfoVOList(Arrays.asList(productInfoVO));
// vo.setData(Arrays.asList(productVO));
// 【封装返回数据】
return ResultVOUtils.success(productVOList);
}
}
虚拟机nginx和本地联调,数据渲染
缺少cookie中携带的openid
http://192.168.210.132/#/order 设置cookie document.cookie='openid=abc123'
虚拟机设置nginx配置文件vim /usr/local/nginx/conf/nginx.conf
-
修改本机的100.68.53.177
location /sell/ { proxy_pass http://100.68.53.177:8080/sell/; }
-
修改域名
server_name sell.com;
修改本机的host
sudo vim /etc/hosts
# 配置项目的访问 192.168.210.132 sell.com
-
重启nginx
ngnix -s reload
ResultEnum
package com.bennyrhys.wechat_order.enums;
import lombok.Getter;
/**
* 返回给前端提示的异常消息
* @Author bennyrhys
* @Date 2020-06-28 10:12
*/
@Getter
public enum ResultEnum {
PRODUCT_NOT_EXIST(10, "商品不存在"),
PRODUCT_STOCK_ERROR(11,"库存不正确"),
ORDER_NOT_EXIST(12, "订单为空"),
ORDERDETAIL_NOT_EXIST(13, "订单详情为空"),
ORDER_STATUS_ERROR(14, "订单状态不正确"),
ORDER_UPDATE_FAIL(15,"订单更新失败"),
ORDER_UPDATE_EMPTY(16,"订单详情为空"),
ORDER_PAY_STATUS_ERROR(17, "订单支付状态不正确"),
;
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
PayStatusEnum
package com.bennyrhys.wechat_order.enums;
import lombok.Getter;
/**
* 支付状态
* @Author bennyrhys
* @Date 2020-06-28 08:00
*/
@Getter
public enum PayStatusEnum {
WAIT(0, "待支付"),
SUCCESS(1, "支付成功")
;
private Integer code;
private String msg;
PayStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
OrderStatusEnum
package com.bennyrhys.wechat_order.enums;
import lombok.Getter;
/**
* 订单状态
* @Author bennyrhys
* @Date 2020-06-28 07:53
*/
@Getter
public enum OrderStatusEnum {
NEW(0, "新订单"),
FINISHED(1, "完结"),
CANCEL(2, "取消"),
;
private Integer code;
private String msg;
OrderStatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
OrderMaster
package com.bennyrhys.wechat_order.daoobject;
import com.bennyrhys.wechat_order.enums.OrderStatusEnum;
import com.bennyrhys.wechat_order.enums.PayStatusEnum;
import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.math.BigDecimal;
import java.util.Date;
/**
* 订单表
* @Author bennyrhys
* @Date 2020-06-28 07:49
*/
@Entity
@Data
// 时间自动更新
@DynamicUpdate
public class OrderMaster {
// 订单id
@Id
private String orderId;
// 买家名字
private String buyerName;
// 买家电话
private String buyerPhone;
// 买家地址
private String buyerAddress;
// 买家微信openid
private String buyerOpenid;
// 订单总金额
private BigDecimal orderAmount;
// 订单状态,默认0新下单
private Integer orderStatus = OrderStatusEnum.NEW.getCode();
// 支付状态,默认0未支付
private Integer payStatus = PayStatusEnum.WAIT.getCode();
// 创建时间 【考虑到时间排序】
private Date createTime;
// 更新时间
private Date updateTime;
}
OrderDetail
package com.bennyrhys.wechat_order.daoobject;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.math.BigDecimal;
/**
* 订单详情
* @Author bennyrhys
* @Date 2020-06-28 08:09
*/
@Entity
@Data
public class OrderDetail {
// 订单详情id
@Id
private String detailId;
// 订单id
private String orderId;
// 商品id
private String productId;
// 商品名称
private String productName;
// 商品价格
private BigDecimal productPrice;
// 商品数量
private Integer productQuantity;
// 商品小图
private String productIcon;
}
OrderMasterRepository
package com.bennyrhys.wechat_order.repository;
import com.bennyrhys.wechat_order.daoobject.OrderMaster;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* 订单
* @Author bennyrhys
* @Date 2020-06-28 08:16
*/
public interface OrderMasterRepository extends JpaRepository<OrderMaster, String> {
/**
* 分页查询一个用户的全部订单
* @param buyerOpenid
* @param pageable
* @return
*/
Page<OrderMaster> findByBuyerOpenid(String buyerOpenid, Pageable pageable);
}
OrderDetailRepository
package com.bennyrhys.wechat_order.repository;
import com.bennyrhys.wechat_order.daoobject.OrderDetail;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* 订单详情
* @Author bennyrhys
* @Date 2020-06-28 08:20
*/
public interface OrderDetailRepository extends JpaRepository<OrderDetail, String> {
/**
* 根据订单id查询订单详情
* 一对多
* @param orderId
* @return
*/
List<OrderDetail> findByOrderId(String orderId);
}
OrderMasterRepositoryTest
package com.bennyrhys.wechat_order.repository;
import com.bennyrhys.wechat_order.daoobject.OrderMaster;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
/**
* @Author bennyrhys
* @Date 2020-06-28 08:27
*/
@SpringBootTest
class OrderMasterRepositoryTest {
@Autowired
OrderMasterRepository repository;
private final String OPENID = "110110";
@Test
public void saveTest() {
OrderMaster orderMaster = new OrderMaster();
orderMaster.setOrderId("12");
orderMaster.setBuyerName("瑞新");
orderMaster.setBuyerAddress("吉林通化");
orderMaster.setBuyerOpenid(OPENID);
orderMaster.setBuyerPhone("123456789123");
orderMaster.setOrderAmount(new BigDecimal(8.8));
OrderMaster result = repository.save(orderMaster);
Assertions.assertNotNull(result);
}
/**
* 分页查询一个用户的全部订单
*/
@Test
public void findByBuyerOpenid() {
PageRequest pageRequest = PageRequest.of(0, 2);
Page<OrderMaster> byBuyerOpenid = repository.findByBuyerOpenid(OPENID, pageRequest);
// System.out.println(byBuyerOpenid.getTotalElements());
Assertions.assertNotEquals(0, byBuyerOpenid.getTotalElements());
}
}
OrderDetailRepositoryTest
package com.bennyrhys.wechat_order.repository;
import com.bennyrhys.wechat_order.daoobject.OrderDetail;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.math.BigDecimal;
import java.util.List;
/**
* 订单详情
* @Author bennyrhys
* @Date 2020-06-28 08:56
*/
@SpringBootTest
class OrderDetailRepositoryTest {
@Autowired
OrderDetailRepository repository;
@Test
public void saveTest() {
OrderDetail orderDetail = new OrderDetail();
orderDetail.setDetailId("2");
orderDetail.setOrderId("11");
orderDetail.setProductIcon("http://xxx.png");
orderDetail.setProductId("11");
orderDetail.setProductName("雪梨");
orderDetail.setProductPrice(new BigDecimal(9.5));
orderDetail.setProductQuantity(2);
OrderDetail result = repository.save(orderDetail);
Assertions.assertNotNull(result);
}
/**
* 根据订单id查订单详情
* 一对多
*/
@Test
public void findByOrderId() {
List<OrderDetail> detailList = repository.findByOrderId("11");
Assertions.assertNotEquals(0, detailList.size());
}
}
为逻辑处理,抽象出来的传输对象。(保证不修改,已有的数据库字段上)
法1:注解,在原有字段进行改动
法2:dto新创建文件,方便维护
OrderMaster注解方式修改【不建议】
// 【新增】一对多关系,订单详情列表。防止映射为空加 @Transient 过滤掉
// @Transient
// private List<OrderDetail> orderDetailList;
com.bennyrhys.wechat_order.dto.OrderDTO 新建dto文件单独生成字段【建议】
package com.bennyrhys.wechat_order.dto;
import com.bennyrhys.wechat_order.daoobject.OrderDetail;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 订单
* 逻辑处理,数据传输的定制化字段
*
* 注意:@Id不用写了,增加的字段不用注解特殊忽略
* @Author bennyrhys
* @Date 2020-06-28 09:27
*/
@Data
public class OrderDTO {
// 订单id
private String orderId;
// 买家名字
private String buyerName;
// 买家电话
private String buyerPhone;
// 买家地址
private String buyerAddress;
// 买家微信openid
private String buyerOpenid;
// 订单总金额
private BigDecimal orderAmount;
// 订单状态,默认0新下单
private Integer orderStatus;
// 支付状态,默认0未支付
private Integer payStatus;
// 创建时间 【考虑到时间排序】
private Date createTime;
// 更新时间
private Date updateTime;
// 【新增】一对多关系,订单详情列表。防止映射为空加 @Transient 过滤掉
private List<OrderDetail> orderDetailList;
}
方便计算减库存的购物车信息
CartDTO
package com.bennyrhys.wechat_order.dto;
import lombok.Data;
/**
* 购物车
* @Author bennyrhys
* @Date 2020-06-28 11:00
*/
@Data
public class CartDTO {
private String productId;
private Integer productQuantity;
public CartDTO(String productId, Integer productQuantity) {
this.productId = productId;
this.productQuantity = productQuantity;
}
}
com.bennyrhys.wechat_order.exception.SellException
package com.bennyrhys.wechat_order.exception;
import com.bennyrhys.wechat_order.enums.ResultEnum;
/**
* 抛异常
* @Author bennyrhys
* @Date 2020-06-28 10:11
*/
public class SellException extends RuntimeException {
private Integer code;
public SellException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
}
}
package com.bennyrhys.wechat_order.enums;
import lombok.Getter;
/**
* 返回给前端提示的异常消息
* @Author bennyrhys
* @Date 2020-06-28 10:12
*/
@Getter
public enum ResultEnum {
PRODUCT_NOT_EXIST(10, "商品不存在"),
PRODUCT_STOCK_ERROR(11,"库存不正确")
;
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
package com.bennyrhys.wechat_order.utils;
import java.util.Random;
/**
* 随机数生成
* 用于无法自增的主键 key
* @Author bennyrhys
* @Date 2020-06-28 10:33
*/
public class KeyUtil {
/**
* 生成唯一主键
* 格式:时间+随机数
* 防止多线程
*/
public static synchronized String genUniqueKey() {
// 六位定长
Integer number = new Random().nextInt(900000)+100000;
return System.currentTimeMillis() + String.valueOf(number);
}
}
package com.bennyrhys.wechat_order.converter;
import com.bennyrhys.wechat_order.daoobject.OrderMaster;
import com.bennyrhys.wechat_order.dto.OrderDTO;
import org.springframework.beans.BeanUtils;
import java.util.List;
import java.util.stream.Collectors;
/**
* 转换器
* @Author bennyrhys
* @Date 2020-06-28 12:49
*/
public class OrderMaster2OrderDTOConverter {
public static OrderDTO convert(OrderMaster orderMaster) {
OrderDTO orderDTO = new OrderDTO();
BeanUtils.copyProperties(orderMaster, orderDTO);
return orderDTO;
}
public static List<OrderDTO> convert(List<OrderMaster> orderMasterList) {
return orderMasterList.stream().map(e ->
convert(e))
.collect(Collectors.toList());
}
}
商品库存
ProductSerice
/**
* 加库存 【传入抽象的 购物车 信息】
* @param cartDTOList
*/
void increaseStock(List<CartDTO> cartDTOList);
/**
* 减库存
*/
void decreaseStock(List<CartDTO> cartDTOList);
ProductServiceImpl
/**
* 加库存 【传入抽象的 购物车 信息】
*
* @param cartDTOList
*/
@Override
@Transactional
public void increaseStock(List<CartDTO> cartDTOList) {
for (CartDTO cartDTO : cartDTOList) {
ProductInfo productInfo = repository.findById(cartDTO.getProductId()).orElse(null);
if (productInfo == null){
throw new SellException(ResultEnum.PRODUCT_NOT_EXIST);
}
Integer result = productInfo.getProductStock() + cartDTO.getProductQuantity();
productInfo.setProductStock(result);
repository.save(productInfo);
}
}
/**
* 减库存
*
* @param cartDTOList
*/
@Override
@Transactional
public void decreaseStock(List<CartDTO> cartDTOList) {
for (CartDTO cartDTO : cartDTOList) {
ProductInfo productInfo = repository.findById(cartDTO.getProductId()).orElse(null);
if (productInfo == null){
throw new SellException(ResultEnum.PRODUCT_NOT_EXIST);
}
Integer result = productInfo.getProductStock() - cartDTO.getProductQuantity();
if (result < 0) {
throw new SellException(ResultEnum.PRODUCT_STOCK_ERROR);
}
productInfo.setProductStock(result);
repository.save(productInfo);
}
}
创建订单
OrderService
package com.bennyrhys.wechat_order.service;
import com.bennyrhys.wechat_order.dto.OrderDTO;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/**
* 订单
* @Author bennyrhys
* @Date 2020-06-28 09:23
*/
public interface OrderService {
/** 创建订单
* 为订单-订单详情,一对多关系抽象出dto */
OrderDTO create(OrderDTO orderDTO);
/** 查询单个订单 */
OrderDTO findOne(String orderId);
/** 查询订单列表
* 注意:包名springframe */
Page<OrderDTO> findList(String buyerOpenId, Pageable pageable);
/** 取消订单 */
OrderDTO cancel(OrderDTO orderDTO);
/** 完结订单 */
OrderDTO finish(OrderDTO orderDTO);
/** 支付订单 */
OrderDTO paid(OrderDTO orderDTO);
}
package com.bennyrhys.wechat_order.service.impl;
import com.bennyrhys.wechat_order.OrderMaster2OrderDTOConverter;
import com.bennyrhys.wechat_order.daoobject.OrderDetail;
import com.bennyrhys.wechat_order.daoobject.OrderMaster;
import com.bennyrhys.wechat_order.daoobject.ProductInfo;
import com.bennyrhys.wechat_order.dto.CartDTO;
import com.bennyrhys.wechat_order.dto.OrderDTO;
import com.bennyrhys.wechat_order.enums.OrderStatusEnum;
import com.bennyrhys.wechat_order.enums.PayStatusEnum;
import com.bennyrhys.wechat_order.enums.ResultEnum;
import com.bennyrhys.wechat_order.exception.SellException;
import com.bennyrhys.wechat_order.repository.OrderDetailRepository;
import com.bennyrhys.wechat_order.repository.OrderMasterRepository;
import com.bennyrhys.wechat_order.service.OrderService;
import com.bennyrhys.wechat_order.service.ProductSerice;
import com.bennyrhys.wechat_order.utils.KeyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.stream.Collectors;
/**
* 订单
* @Author bennyrhys
* @Date 2020-06-28 09:45
*/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private ProductSerice productSerice;
@Autowired
private OrderDetailRepository orderDetailRepository;
@Autowired
private OrderMasterRepository orderMasterRepository;
/**
* 创建订单
* 为订单-订单详情,一对多关系抽象出dto
*
* @param orderDTO
*/
@Override
@Transactional
public OrderDTO create(OrderDTO orderDTO) {
BigDecimal orderAmount = new BigDecimal(BigInteger.ZERO);
String orderId = KeyUtil.genUniqueKey();
// List<CartDTO> cartDTOList = new ArrayList<>();
// 1. 查询商品(数量、金额)
for (OrderDetail orderDetail : orderDTO.getOrderDetailList()) {
ProductInfo productInfo = productSerice.findOne(orderDetail.getProductId());
if (productInfo == null) {
throw new SellException(ResultEnum.PRODUCT_NOT_EXIST);
}
// 2. 计算订单总价 【注意productInfo才有价格】
orderAmount = productInfo.getProductPrice()
.multiply(new BigDecimal(orderDetail.getProductQuantity()))
.add(orderAmount);
// 订单详情入库【注意:前端不会传来全部所需字段。随机数id】
// 对象拷贝 spring提供的简便方法Info->Detail
BeanUtils.copyProperties(productInfo, orderDetail);
orderDetail.setDetailId(KeyUtil.genUniqueKey());
orderDetail.setOrderId(orderId);
orderDetailRepository.save(orderDetail);
// CartDTO cartDTO = new CartDTO(orderDetail.getProductId(), orderDetail.getProductQuantity());
// cartDTOList.add(cartDTO);
}
// 3. 写入订单数据库(orderMaster、orderDetail)
OrderMaster orderMaster = new OrderMaster();
// 拷贝对象 【注意:属性为null也会被拷贝,调整顺序先拷贝】
BeanUtils.copyProperties(orderDTO, orderMaster);
orderMaster.setOrderId(orderId);
orderMaster.setOrderAmount(orderAmount);
// 增加因拷贝被覆盖的默认状态值
orderMaster.setOrderStatus(OrderStatusEnum.NEW.getCode());
orderMaster.setPayStatus(PayStatusEnum.WAIT.getCode());
orderMasterRepository.save(orderMaster);
// 4. 扣库存 【库存一次修改,所以要获取购物车list 法1:上for cartDTO直接list.add 法2:lamdba】
// 并发下 redis锁机制防止超卖
List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream().map(e ->
new CartDTO(e.getProductId(), e.getProductQuantity()))
.collect(Collectors.toList());
productSerice.decreaseStock(cartDTOList);
return orderDTO;
}
/**
* 查询单个订单
*
* @param orderId
*/
@Override
public OrderDTO findOne(String orderId) {
OrderMaster orderMaster = orderMasterRepository.findById(orderId).orElse(null);
if (orderMaster == null) {
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
List<OrderDetail> orderDetailList = orderDetailRepository.findByOrderId(orderId);
if (CollectionUtils.isEmpty(orderDetailList)) {
throw new SellException(ResultEnum.ORDERDETAIL_NOT_EXIST);
}
OrderDTO orderDTO = new OrderDTO();
BeanUtils.copyProperties(orderMaster, orderDTO);
orderDTO.setOrderDetailList(orderDetailList);
return orderDTO;
}
/**
* 查询订单列表
* 注意:包名springframe
*
* @param buyerOpenId
* @param pageable
*/
@Override
public Page<OrderDTO> findList(String buyerOpenId, Pageable pageable) {
Page<OrderMaster> orderMasterPage = orderMasterRepository.findByBuyerOpenid(buyerOpenId, pageable);
// 不查详情,也无需判空
// 创建类型转换DTO
List<OrderDTO> orderDTOList = OrderMaster2OrderDTOConverter.convert(orderMasterPage.getContent());
Page<OrderDTO> orderDTOS = new PageImpl<OrderDTO>(orderDTOList, pageable, orderMasterPage.getTotalElements());
return orderDTOS;
}
/**
* 取消订单
*
* @param orderDTO
*/
@Override
@Transactional
public OrderDTO cancel(OrderDTO orderDTO) {
// 状态转换
OrderMaster orderMaster = new OrderMaster();
// 判断订单状态
if (!orderDTO.getOrderStatus().equals(OrderStatusEnum.NEW.getCode())) {
log.error("【取消订单,订单状态不正确,orderId={}, orderStatus={}】",orderDTO.getOrderId(), orderDTO.getOrderStatus());
throw new SellException(ResultEnum.ORDER_STATUS_ERROR);
}
// 修改订单状态
orderDTO.setOrderStatus(OrderStatusEnum.CANCEL.getCode());
BeanUtils.copyProperties(orderDTO, orderMaster); // 注意状态修改时机
OrderMaster updateResult = orderMasterRepository.save(orderMaster);
if (updateResult == null) {
log.info("【取消订单,更新失败 orderMaster={}】",orderMaster);
throw new SellException(ResultEnum.ORDER_UPDATE_FAIL);
}
// 返还库存
if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) {
log.error("【取消订单,订单详情为空,orderDTO={}】",orderDTO);
throw new SellException(ResultEnum.ORDER_UPDATE_EMPTY);
}
List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream()
.map(e -> new CartDTO(e.getProductId(), e.getProductQuantity()))
.collect(Collectors.toList());
productSerice.increaseStock(cartDTOList);
// 如果已支付需要退款
if(orderDTO.getPayStatus().equals(PayStatusEnum.SUCCESS.getCode())) {
// TODO
}
return orderDTO;
}
/**
* 完结订单
*
* @param orderDTO
*/
@Override
@Transactional
public OrderDTO finish(OrderDTO orderDTO) {
// 判断订单状态
if (!orderDTO.getOrderStatus().equals(OrderStatusEnum.NEW.getCode())) {
log.error("【完结订单:订单状态不正确 orderId={}, orderStatus={}】",orderDTO.getOrderId(), orderDTO.getOrderStatus());
throw new SellException(ResultEnum.ORDER_STATUS_ERROR);
}
// 修改订单状态
orderDTO.setOrderStatus(OrderStatusEnum.FINISHED.getCode());
OrderMaster orderMaster = new OrderMaster();
BeanUtils.copyProperties(orderDTO, orderMaster);
OrderMaster updateResult = orderMasterRepository.save(orderMaster);
if (updateResult == null) {
log.info("【完结订单:更新失败 orderMaster={}】",orderMaster);
throw new SellException(ResultEnum.ORDER_UPDATE_FAIL);
}
return orderDTO;
}
/**
* 支付订单
*
* @param orderDTO
*/
@Override
@Transactional
public OrderDTO paid(OrderDTO orderDTO) {
// 判断订单状态
if (!orderDTO.getOrderStatus().equals(OrderStatusEnum.NEW.getCode())) {
log.error("【订单支付成功】订单状态不正确 orderId={}, orderStatus={}",orderDTO.getOrderId(), orderDTO.getOrderStatus());
throw new SellException(ResultEnum.ORDER_STATUS_ERROR);
}
// 判断支付状态
if (!orderDTO.getPayStatus().equals(PayStatusEnum.WAIT.getCode())) {
log.error("【订单支付成功】订单支付状态不正确 orderDTO={}",orderDTO);
throw new SellException(ResultEnum.ORDER_PAY_STATUS_ERROR);
}
// 修改支付状态
orderDTO.setPayStatus(PayStatusEnum.SUCCESS.getCode());
OrderMaster orderMaster = new OrderMaster();
BeanUtils.copyProperties(orderDTO, orderMaster);
OrderMaster updateResult = orderMasterRepository.save(orderMaster);
if (updateResult == null) {
log.info("【订单支付成功:更新失败 orderMaster={}】",orderMaster);
throw new SellException(ResultEnum.ORDER_UPDATE_FAIL);
}
return orderDTO;
}
}
package com.bennyrhys.wechat_order.service.impl;
import com.bennyrhys.wechat_order.daoobject.OrderDetail;
import com.bennyrhys.wechat_order.dto.OrderDTO;
import com.bennyrhys.wechat_order.enums.OrderStatusEnum;
import com.bennyrhys.wechat_order.enums.PayStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import java.util.ArrayList;
import java.util.List;
/**
* 订单
* @Author bennyrhys
* @Date 2020-06-28 11:26
*/
@SpringBootTest
@Slf4j
class OrderServiceImplTest {
@Autowired
private OrderServiceImpl orderService;
private final String BUYER_OPENID = "110110";
private final String ORDER_ID = "1593317227294809400";
@Test
void create() {
OrderDTO orderDTO = new OrderDTO();
orderDTO.setBuyerName("瑞新");
orderDTO.setBuyerAddress("吉林通化");
orderDTO.setBuyerPhone("123456789123");
orderDTO.setBuyerOpenid(BUYER_OPENID);
// 购物车
List<OrderDetail> orderDetailList = new ArrayList<>();
OrderDetail od = new OrderDetail();
od.setProductId("11");
od.setProductQuantity(1);
orderDetailList.add(od);
OrderDetail od2 = new OrderDetail();
od2.setProductId("22");
od2.setProductQuantity(2);
orderDetailList.add(od2);
orderDTO.setOrderDetailList(orderDetailList);
OrderDTO result = orderService.create(orderDTO);
// log.info("【创建订单】result={}", result);
Assertions.assertNotNull(result);
}
@Test
void findOne() {
OrderDTO result = orderService.findOne(ORDER_ID);
log.info("【单个订单】result={}", result);
Assertions.assertEquals(ORDER_ID, result.getOrderId());
}
@Test
void findList() {
PageRequest pageRequest = PageRequest.of(0, 2);
Page<OrderDTO> orderDTOPage = orderService.findList(BUYER_OPENID,pageRequest);
Assertions.assertNotEquals(0, orderDTOPage.getTotalElements());
}
@Test
void cancel() {
OrderDTO orderDTO = orderService.findOne(ORDER_ID);
OrderDTO result = orderService.cancel(orderDTO);
Assertions.assertEquals(OrderStatusEnum.CANCEL.getCode(), result.getOrderStatus());
}
@Test
void finish() {
OrderDTO orderDTO = orderService.findOne(ORDER_ID);
OrderDTO result = orderService.finish(orderDTO);
Assertions.assertEquals(OrderStatusEnum.FINISHED.getCode(), result.getOrderStatus());
}
@Test
void paid() {
OrderDTO orderDTO = orderService.findOne(ORDER_ID);
OrderDTO result = orderService.paid(orderDTO);
Assertions.assertEquals(PayStatusEnum.SUCCESS.getCode(), result.getPayStatus());
}
}
Date2LongSerializer时间转换
package com.bennyrhys.wechat_order.utils.serializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.Date;
/**
* 处理时间 毫秒变秒
* @Author bennyrhys
* @Date 2020-06-28 18:28
*/
public class Date2LongSerializer extends JsonSerializer<Date> {
@Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeNumber(date.getTime() / 1000);
}
}
OrderDTO字段类型指定类进行计算
// 创建时间 【考虑到时间排序】 @JsonSerialize 毫秒变秒
@JsonSerialize(using = Date2LongSerializer.class)
private Date createTime;
// 更新时间
@JsonSerialize(using = Date2LongSerializer.class)
private Date updateTime;
法1:null则不显示
法2:在返回值OrderDTO中赋初值
null则不显示
OrderDTO
// 保障不给前端返回为null的字段 【一个个配太傻,配置文件统一配】
//@JsonInclude(JsonInclude.Include.NON_NULL)
public class OrderDTO {
配置文件控制全局
# 配置json为nll的字段 不显示
jackson:
default-property-inclusion: non_null
空列表=[]
// 【新增】一对多关系,订单详情列表。防止映射为空加 @Transient 过滤掉
private List<OrderDetail> orderDetailList = new ArrayList<>();
"orderDetailList": []
空字符串=“”
BuyerOrderController
// return ResultVOUtil.success(orderDTOPage.getContent());
ResultVO resultVO = new ResultVO();
resultVO.setCode(0);
return resultVO;
ResultVO
// 提示
private String msg="";
{
"code": 0,
"msg": "",
"data": null
}
service辅助,校验openid是否为本人操作
订单详情、取消订单(确认身份)
BuyerService
package com.bennyrhys.wechat_order.service;
import com.bennyrhys.wechat_order.dto.OrderDTO;
/**
* 买家
* 为控制台提供安全性保证
* @Author bennyrhys
* @Date 2020-06-29 08:31
*/
public interface BuyerService {
// 查询一个订单
OrderDTO findOrderOne(String openid, String orderId);
// 取消一个订单
OrderDTO cancelOrder(String openid, String orderId);
}
BuyerServiceImpl
package com.bennyrhys.wechat_order.service.impl;
import com.bennyrhys.wechat_order.dto.OrderDTO;
import com.bennyrhys.wechat_order.enums.ResultEnum;
import com.bennyrhys.wechat_order.exception.SellException;
import com.bennyrhys.wechat_order.service.BuyerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 买家
* 为控制台提供安全性保证
* @Author bennyrhys
* @Date 2020-06-29 08:33
*/
@Service
@Slf4j
public class BuyerServiceImpl implements BuyerService {
@Autowired
OrderServiceImpl orderService;
@Override
public OrderDTO findOrderOne(String openid, String orderId) {
return checkOrderOwner(openid, orderId);
}
@Override
public OrderDTO cancelOrder(String openid, String orderId) {
OrderDTO orderDTO = checkOrderOwner(openid, orderId);
if (orderDTO == null) {
log.error("【取消订单】查不到该订单。orderId={}", orderId);
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
return orderService.cancel(orderDTO);
}
/**
* 判断是否是自己的订单,通过openid
*/
public OrderDTO checkOrderOwner(String openid, String orderId) {
OrderDTO orderDTO = orderService.findOne(orderId);
if (orderDTO == null) {
return null;
}
// 判断是否是自己的订单,通过openid
if(!orderDTO.getBuyerOpenid().equalsIgnoreCase(openid)) {
log.error("【查询订单】订单的openid不一致。openid={},orderId", openid, orderId);
throw new SellException(ResultEnum.ORDER_OWNER_ERROR);
}
return orderDTO;
}
}
BuyerOrderController
package com.bennyrhys.wechat_order.controller;
import com.bennyrhys.wechat_order.VO.ResultVO;
import com.bennyrhys.wechat_order.converter.OrderForm2OrderDTOConverter;
import com.bennyrhys.wechat_order.dto.OrderDTO;
import com.bennyrhys.wechat_order.enums.ResultEnum;
import com.bennyrhys.wechat_order.exception.SellException;
import com.bennyrhys.wechat_order.form.OrderForm;
import com.bennyrhys.wechat_order.service.BuyerService;
import com.bennyrhys.wechat_order.service.OrderService;
import com.bennyrhys.wechat_order.utils.ResultVOUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 买家订单
* @Author bennyrhys
* @Date 2020-06-28 15:35
*/
@RestController
@RequestMapping("/buyer/order")
@Slf4j
public class BuyerOrderController {
@Autowired
private OrderService orderService;
@Autowired
private BuyerService buyerService;
/**
* 创建订单
* @param orderForm
* @param bindingResult
* @return
*/
@PostMapping("create")
public ResultVO<Map<String, String>> create(@Valid OrderForm orderForm,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
log.error("【创建定单】参数不正确orderForm={}", orderForm);
// 捕获表单异常并返回
throw new SellException(ResultEnum.PARAM_ERROR.getCode(),
bindingResult.getFieldError().getDefaultMessage());
}
// 转换orderForm->orderDTO
OrderDTO orderDTO = OrderForm2OrderDTOConverter.convert(orderForm);
if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) {
log.error("【创建订单】购物车不能为空");
throw new SellException(ResultEnum.CART_EMPTY);
}
OrderDTO createResult = orderService.create(orderDTO);
Map<String, String> map = new HashMap<>();
map.put("orderId", createResult.getOrderId());
return ResultVOUtil.success(map);
}
/**
* 订单列表
* http://localhost:8080/sell/buyer/order/list?openid= ew3euwhd7sjw9diwkq&page=&size=
*/
@GetMapping("/list")
public ResultVO<List<OrderDTO>> list(@RequestParam("openid") String openid,
@RequestParam(value = "page", defaultValue = "0") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size) {
if (StringUtils.isEmpty(openid)) {
log.error("【查询订单列表】订单列表为空");
throw new SellException(ResultEnum.PARAM_ERROR);
}
PageRequest pageRequest = PageRequest.of(page, size);
Page<OrderDTO> orderDTOPage = orderService.findList(openid, pageRequest);
return ResultVOUtil.success(orderDTOPage.getContent());
}
/**
* 订单详情
*/
@GetMapping("/detail")
public ResultVO<OrderDTO> detail(@RequestParam("openid") String openid,
@RequestParam("orderId") String orderId) {
// 直接访问资源,越权访问
// TODO 不安全的做法,已改进
OrderDTO orderDTO = buyerService.findOrderOne(openid, orderId);
return ResultVOUtil.success(orderDTO);
}
/**
* 取消订单
*/
@PostMapping("/cancel")
public ResultVO cancel(@RequestParam("openid") String openid,
@RequestParam("orderId") String orderId) {
// TODO 不安全的做法,已改进
buyerService.cancelOrder(openid, orderId);
return ResultVOUtil.success();
}
}
使用natapp映射本地环境
官方文档 https://mp.weixin.qq.com/wiki
调试内网穿透 https://natapp.cn
第三方SDK . https://github.com/Wechat-Group/weixin-java-tools
支付(需要企业资质)
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
网页授权
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
授权回调本地环境(内网穿透-ssl+https)
填写域名。公众号设置-功能设置-设置JS接口安全域名后,公众号开发者可在该域名下调用微信开放的JS接口。
下载认证文件,拷贝到本地静态资源目录(注意/sell前缀先撤掉,直接访问验证),验证通过后侧回信息
个体用户没有获取个人信息权限,公众号后台测试号可以啊。再配置一遍js和个人信息的域名安全检测
注意:code仅能使用一次,5分钟后失效
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxc10086806f4c0df0&redirect_uri=http://bennyrhys.mynatapp.cc/sell/weixin/auth&response_type=code&scope=snsapi_base&state=STATE#wechat_redirec
snsapi_base 和 snsapi_userinfo两种获取信息
动态获取code拼接
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxc10086806f4c0df0&redirect_uri=http://bennyrhys.mynatapp.cc/sell/weixin/auth&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirec
package com.bennyrhys.wechat_order.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 公众号手动获取openid
* @Author bennyrhys
* @Date 2020-06-29 16:27
*/
@RestController
@RequestMapping("/weixin")
@Slf4j
public class WeixinController {
@GetMapping("/auth")
public void auth(@RequestParam("code") String code) {
log.info("进入微信授权。。。");
log.info("code={}",code);
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wxc10086806f4c0df0&secret=bdcb164ea61b4b1cc6846cd549325898&code="+ code +"&grant_type=authorization_code";
// 接收 json
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url, String.class);
log.info("response={}",response);
// oWBZiwSvnfae70D6wESTWbc0cDi4
}
@GetMapping("/test")
public String test() {
return "lai";
}
}
微信小程序:weixin-java-miniapp
微信支付:weixin-java-pay
微信开放平台:weixin-java-open
公众号(包括订阅号和服务号):weixin-java-mp
企业号/企业微信:weixin-java-cp
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>(不同模块参考下文)</artifactId>
<version>3.8.0</version>
</dependency>
weixin-java-mp,OAuth2网页授权,
配置文件application.yml
# 配置微信公众号
wechat:
mpAppId: wxc10086806f4c0df0
mpAppSecret: bdcb164ea61b4b1cc6846cd549325898
配置WechatMpConfig
package com.bennyrhys.wechat_order.config;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* 微信公众号sdk配置
* @Author bennyrhys
* @Date 2020-06-30 07:12
*/
@Component
public class WechatMpConfig {
@Autowired
private WechatAccountConfig wechatAccountConfig;
@Bean
public WxMpService wxMpService() {
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
return wxMpService;
}
@Bean
public WxMpConfigStorage wxMpConfigStorage(){
WxMpDefaultConfigImpl wxMpConfigStorage = new WxMpDefaultConfigImpl();
// 从配置文件读
wxMpConfigStorage.setAppId(wechatAccountConfig.getMpAppId());
wxMpConfigStorage.setSecret(wechatAccountConfig.getMpAppSecret());
return wxMpConfigStorage;
}
}
账户信息WechatAccountConfig
package com.bennyrhys.wechat_order.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 微信账号相关
* 获取配置文件的openid
* @Author bennyrhys
* @Date 2020-06-30 07:23
*/
@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {
private String mpAppId;
private String mpAppSecret;
}
控制台
package com.bennyrhys.wechat_order.controller;
import com.bennyrhys.wechat_order.enums.ResultEnum;
import com.bennyrhys.wechat_order.exception.SellException;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.net.URLEncoder;
/**
* 公众号获取openid-第三方sdk
* @Author bennyrhys
* @Date 2020-06-29 21:27
*/
//@RestController // 返回的是json,要页面跳转得用@Controller
@Controller
@RequestMapping("/wechat")
@Slf4j
public class WechatController {
@Autowired
private WxMpService wxMpService;
/**
* 网页授权
* http://bennyrhys.mynatapp.cc/sell/wechat/authorize?returnUrl=http://www.imooc.com
* @param returnUrl
* @return
*/
@GetMapping("/authorize")
public String authorize(@RequestParam("returnUrl") String returnUrl) {
// 1. 公众号mp配置【公共config】
// 2. 调用方法 网页授权获取code【重定向url(配置项目地址+方法地址),scope,state】
String url = "http://bennyrhys.mynatapp.cc/sell/wechat/userInfo";
String redirectUrl = wxMpService.oauth2buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_USERINFO, URLEncoder.encode(returnUrl));
log.info("【微信网页授权】获取code,result={}", redirectUrl);
return "redirect:"+redirectUrl;
}
/**
* 获取用户信息-重定向的目的
*/
@GetMapping("/userInfo")
public String userInfo(@RequestParam("code") String code,
@RequestParam("state") String returnUrl) {
WxMpOAuth2AccessToken wxMpOAuth2AccessToken = new WxMpOAuth2AccessToken();
try {
wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
}catch (WxErrorException e) {
log.error("【微信网页授权】{}", e);
throw new SellException(ResultEnum.WECHAT_MP_ERROR.getCode(), e.getError().getErrorMsg());
}
// 获取openid
String openid = wxMpOAuth2AccessToken.getOpenId();
return "redirect:" + returnUrl + "?openid=" + openid;
}
}
商铺信息api放到静态资源目录,直接渲染数据
虚拟机代码配置前端跳转:cd /opt/code/sell_fe_buyer/config/index.js
修改前端路径
[root@localhost config]# vim index.js
sellUrl: 'http://127.0.0.1',
openidUrl: 'http://sell.springboot.cn/sell/wechat/authorize',
wechatPayUrl: 'http://127.0.0.1'
修改后
sellUrl: 'http://sell.com',
openidUrl: 'http://bennyrhys.mynatapp.cc/sell/wechat/authorize',
wechatPayUrl: 'http://127.0.0.1'
重新构建前端项目
[root@localhost config]# cd ..
[root@localhost sell_fe_buyer]# pwd
/opt/code/sell_fe_buyer
[root@localhost sell_fe_buyer]# npm run build
// 生成文件覆盖网站目录文件
[root@localhost sell_fe_buyer]# ls -al dist/
总用量 8
drwxr-xr-x. 3 root root 38 6月 30 00:42 .
drwxr-xr-x. 10 root root 4096 4月 21 2017 ..
-rw-r--r--. 1 root root 616 6月 30 00:43 index.html
drwxr-xr-x. 4 root root 27 6月 30 00:43 static
[root@localhost sell_fe_buyer]# cp -r dist/* /opt/data/wwwroot/sell/
cp:是否覆盖"/opt/data/wwwroot/sell/index.html"? y
cp:是否覆盖"/opt/data/wwwroot/sell/static/css/reset.css"? y
cp:是否覆盖"/opt/data/wwwroot/sell/static/css/app.fc95ecb15d823cfb3172a7b93aaa741d.css"? y
cp:是否覆盖"/opt/data/wwwroot/sell/static/js/vendor.e8bcf9a796b8d5dbca42.js"? y
场景
注意:以下操作找个wifi,校园网不稳定
微信访问sell.com,手机端无法访问host映射在电脑上。
工具:charles安装配置 for Mac,win的fiddler
解决访问:通过抓包和代理请求将微信请求映射到电脑访问
确认手机ping通,设置http代理为电脑ip端口(8888)
手机微信访问:sell.com
支付
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
第三方SDK(用于各第三方SDK支付)
https://github.com/Pay-Group/best-pay-sdk
- 授权和支付分开调试,授权用微信官方测试账号,支付用借我的账号,写死openid调试
- 微信授权 https://www.imooc.com/article/70497
- 微信支付 https://www.imooc.com/article/31607
- 扫码登录调试文档 见doc目录下的open.md
微信内H5调起支付,写到后端static/pay.html
解决缺少total_fee:
统一下单有total_fee字段返回预支付id就是total_fee,就是指预支付id不正确
解决当前页面url未注册:
公众平台-微信支付,开发配置,支付url最多5个,备案
js静态模板下订单变动态,引入freemark模板。控制台返回ModelAndView路径。相应建立项目freemark文件
配置账户信息application.yml
# 配置微信公众号
wechat:
# 用户授权-测试号
# mpAppId: wxc10086806f4c0df0
# mpAppSecret: bdcb164ea61b4b1cc6846cd549325898
# 支付号-不用secret
mpAppId: wxd898fcb01713c658
mchId: 1483469312
mchKey: C5245D70627C1F8E9964D494B0735025
keyPath: /var/wechat-cert/h5.p12
notifyUrl: http/sell/pay/notify # 最好用外网地址【微信异步通知】
调用信息WechatAccountConfig
package com.bennyrhys.wechat_order.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 微信账号相关
* 获取配置文件的openid
* @Author bennyrhys
* @Date 2020-06-30 07:23
*/
@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {
private String mpAppId;
private String mpAppSecret;
/*商户号*/
private String mchId;
/*商户秘钥*/
private String mchKey;
/*商户证书路径*/
private String keyPath;
/*微信异步通知*/
private String notifyUrl;
}
支付配置WechatPayConfig
package com.bennyrhys.wechat_order.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 微信账号相关
* 获取配置文件的openid
* @Author bennyrhys
* @Date 2020-06-30 07:23
*/
@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {
private String mpAppId;
private String mpAppSecret;
/*商户号*/
private String mchId;
/*商户秘钥*/
private String mchKey;
/*商户证书路径*/
private String keyPath;
/*微信异步通知*/
private String notifyUrl;
}
转json看日志方便JsonUtil
package com.bennyrhys.wechat_order.utils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Json格式化工具
* @Author bennyrhys
* @Date 2020-06-30 20:05
*/
public class JsonUtil {
// 对象格式化为json格式
public static String toJson(Object object) {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setPrettyPrinting();
Gson gson = gsonBuilder.create();
return gson.toJson(object);
}
}
控制台模拟创建PayController
package com.bennyrhys.wechat_order.controller;
import com.bennyrhys.wechat_order.dto.OrderDTO;
import com.bennyrhys.wechat_order.enums.ResultEnum;
import com.bennyrhys.wechat_order.exception.SellException;
import com.bennyrhys.wechat_order.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
/**
* 支付
* @Author bennyrhys
* @Date 2020-06-30 16:30
*/
@Controller
@RequestMapping("pay")
@Slf4j
public class PayController {
@Autowired
private OrderService orderService;
@GetMapping("create")
public ModelAndView create(@RequestParam("orderId") String orderId,
@RequestParam("returnUrl") String returnUrl) {
// 1. 查询订单
OrderDTO orderDTO = orderService.findOne(orderId);
if (orderDTO == null) {
log.info("【支付】订单为空");
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
// 发起支付【支付逻辑写到paySerive】
return new ModelAndView("pay/create");
}
}
测试模拟创建订单,PayServiceImplTest
package com.bennyrhys.wechat_order.service.impl;
import com.bennyrhys.wechat_order.dto.OrderDTO;
import com.bennyrhys.wechat_order.service.OrderService;
import com.bennyrhys.wechat_order.service.PayService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
/**
* 微信支付
* @Author bennyrhys
* @Date 2020-06-30 19:48
*/
@SpringBootTest
@Slf4j
class PayServiceImplTest {
@Autowired
private PayService payService;
@Autowired
private OrderService orderService;
@Test
void create() {
OrderDTO orderDTO = orderService.findOne("1593336982853846059");
payService.create(orderDTO);
}
}
静态请求。根据订单的返回信息修改js静态的请求参数。将支付请求在微信上访问请求
static/pay.html
<script>
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"wx2421b1c4370ec43b", //公众号名称,由商户传入
"timeStamp":"1395712654", //时间戳,自1970年以来的秒数
"nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串
"package":"prepay_id=u802345jgfjsdfgsdg888",
"signType":"MD5", //微信签名方式:
"paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ){
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
});
}
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
</script>
引入freemark模板,${}方式动态获取数据
虚拟机配置前端代码index.js.外网的支付域名http://sell/wechat/pay/create,编译重新覆盖项目
此时未修改状态,支付后还是显示支付
通过charies在手机选择商品直接支付
PayController
package com.bennyrhys.wechat_order.controller;
import com.bennyrhys.wechat_order.dto.OrderDTO;
import com.bennyrhys.wechat_order.enums.ResultEnum;
import com.bennyrhys.wechat_order.exception.SellException;
import com.bennyrhys.wechat_order.service.OrderService;
import com.bennyrhys.wechat_order.service.PayService;
import com.lly835.bestpay.model.PayRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import java.util.Map;
/**
* 支付
* @Author bennyrhys
* @Date 2020-06-30 16:30
*/
@Controller
@RequestMapping("pay")
@Slf4j
public class PayController {
@Autowired
private OrderService orderService;
@Autowired
private PayService payService;
@GetMapping("create")
public ModelAndView create(@RequestParam("orderId") String orderId,
@RequestParam("returnUrl") String returnUrl,
Map<String,Object> map){
// 1. 查询订单
OrderDTO orderDTO = orderService.findOne(orderId);
if (orderDTO == null) {
log.info("【支付】订单为空");
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
// 2. 发起支付【支付逻辑写到paySerive】
PayRequest payRequest = payService.create(orderDTO);
// 参数注入模板动态模板
map.put("orderId", "1111");
map.put("payRequest",payRequest);
map.put("returnUrl", returnUrl);
return new ModelAndView("pay/create", map);
}
}
create.ftlh
<#--这个文件名后缀 ftl是旧版-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>#[$]#</title>
</head>
<body>
<h1>这是模板内容${orderId}</h1>
<h1>${payRequest.openid}</h1>
</body>
</html>
<#--静->动-->
<script>
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"${payResponse.appId}", //公众号名称,由商户传入
"timeStamp":"${payResponse.timeStamp}", //时间戳,自1970年以来的秒数
"nonceStr":"${payResponse.nonceStr}", //随机串
"package":"${payResponse.packAge}",
"signType":"MD5", //微信签名方式:
"paySign":"${payResponse.paySign}" //微信签名
},
function(res){
// if(res.err_msg == "get_brand_wcpay_request:ok" ) {
// } // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
location.href = "${returnUrl}";
}
);
}
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
</script>
支付成功不能用前端通知,js微信团队说成功通知不可靠,并且前端代码容易被篡改
yml的配置地址要和控制台请求的一致notifyUrl: http/sell/pay/notify # 最好用外网地址【微信异步通知】
异步通知传入的xml中是微信提供的流水号
收到通知,修改支付订单状态。写到service中,处理金额比较差值
返回确认收到。防止微信一直发异步通知:收到异步通过修改订单支付状态后告知微信已经处理。API支付结果
PayService
PayResponse notify(String notifyData);
PayServiceImpl
/**
* 异步通知
* @param notifyData
* @return
*/
@Override
public PayResponse notify(String notifyData) {
//1. 验证签名
//2. 支付的状态 best替做了前两件事情
//3. 支付金额
//4. 支付人(下单人 == 支付人)
PayResponse payResponse = bestPayService.asyncNotify(notifyData);
log.info("【微信支付】异步通知,PayResponse={}", JsonUtil.toJson(payResponse));
// 查订单
OrderDTO orderDTO = orderService.findOne(payResponse.getOrderId());
// 判断订单是否存在
if (orderDTO == null) {
log.error("【微信支付】异步通知,订单不存在.orderId={}",payResponse.getOrderId());
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
// 判断金额是否一致【金额比较不能Bigdecim和double比较,并且转换后还有小数不一致情况,所以相减解决】compareTo == 0 代表金额一致
if (!MathUtil.equals(payResponse.getOrderAmount(), orderDTO.getOrderAmount().doubleValue())) {
log.error("【微信支付】异步通知,订单金额不一致.orderId={},微信通知金额={},系统金额={}",
payResponse.getOrderId(),
payResponse.getOrderAmount(),
orderDTO.getOrderAmount());
throw new SellException(ResultEnum.WXPAY_NOTIFY_MONEY_VERIFY_ERROR);
}
// 修改订单支付状态
orderService.paid(orderDTO);
return payResponse;
}
处理金额的数学
package com.bennyrhys.wechat_order.utils;
/**
* 金额计算
* @Author bennyrhys
* @Date 2020-07-01 09:59
*/
public class MathUtil {
private static final Double MONEY_RANGE = 0.01;
/*比较两金额相减,判断金额一致*/
public static boolean equals(Double d1, Double d2) {
double result = Math.abs(d1 - d2);
return (result < MONEY_RANGE) ? true : false;
}
}
PayController
/**
* 微信异步通知
* 会传入xml的字符串
* 接收到通知,同步返回xml确认信息
* @param notifyData
*/
@PostMapping("/notify")
public ModelAndView notify(@RequestBody String notifyData) {
payService.notify(notifyData);
//返回给微信处理结果
return new ModelAndView("pay/success");
}
返回的信息templates/pay/success.ftl
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
买家和卖家都可以取消订单
一年内可退
退款拆分次数不可超50次
证书双向绑定(微信支付-账户-安全,下载证书,放到本地配置路径,必须有可读权限)
PayService
/*退款*/
RefundResponse refund(OrderDTO orderDTO);
PayServiceImpl
/*退款*/
@Override
public RefundResponse refund(OrderDTO orderDTO) {
RefundRequest refundRequest = new RefundRequest();
refundRequest.setOrderId(orderDTO.getOrderId());
refundRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());
refundRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);
log.info("【微信退款】request={}",refundRequest);
RefundResponse refundResponse = bestPayService.refund(refundRequest);
log.info("【微信退款】response={}", refundResponse);
return refundResponse;
}
测试PayServiceImplTest
// 退款
@Test
void refund() {
// 已支付订单号
OrderDTO orderDTO = orderService.findOne("1593336982853846059");
payService.refund(orderDTO);
}
补充退款逻辑OrderServiceImpl
// 如果已支付需要退款
if(orderDTO.getPayStatus().equals(PayStatusEnum.SUCCESS.getCode())) {
payService.refund(orderDTO);
}
return orderDTO;
- 后台图片上传使用又拍云,又拍云免费体验