springboot+支付宝+redis实现商品购买和订单超时处理

写在前面:做开发尤其是Java开发的朋友肯定都想去阿里这样的大厂(反正作者想去哈哈~),挑战“三高”以及各种极限到不能再极限的业务!那么今天作者就从这类电商企业最基本也是最核心的业务切入,简单实现一个订单支付业务

说人话:就是把交易信息保存起来,不然您的年度账单哪来

前期准备

支付开放平台
首先需要登录支付宝的支付开放平台并生成一个沙箱支付的账号

在这里插入图片描述


这里需要配置自己本地的回调地址

在这里插入图片描述

这里作者已经注册好了,然后简单了解一下支付的开放api就可以开始了

在这里插入图片描述

开发三板斧:约定>配置>编码

项目结构

在这里插入图片描述


数据库表结构

CREATE TABLE `product_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `out_trade_no` varchar(255) DEFAULT NULL COMMENT '订单编号',
  `subject` varchar(255) DEFAULT NULL COMMENT '订单名',
  `total_amount` double(10,2) DEFAULT NULL COMMENT '订单金额',
  `order_status` tinyint(2) DEFAULT '0' COMMENT '订单状态(0:未支付,1:已支付,3:订单超时)',
  `product_code` varchar(255) DEFAULT NULL COMMENT '产品编号',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.excesys</groupId>
    <artifactId>springboot-pay-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-pay-service</name>
    <description>springboot集成支付</description>
    <properties>
        <java.version>8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.belerweb</groupId>
            <artifactId>pinyin4j</artifactId>
            <version>2.5.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.78</version>
        </dependency>
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>3.7.110.ALL</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.16</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

配置

server:
  port: 8081
spring:
  application:
    name: springboot-pay-service
  main:
    allow-bean-definition-overriding: true
  datasource:
    url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  thymeleaf:
    cache: false
  redis:
    host: localhost
    password:  
    database: 15
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: off
  type-aliases-package: com.excesys.pojo
#  ----------阿里云-支付宝相关配置-----------
alipay:
  #应用ID
  appId: 你的应用ID
  #应用公钥
  alipayPublicKey: 你的应用公钥
  #应用私钥
  privateKey: 你的应用私钥
  #异步通知回调地址
  notifyUrl: http://127.0.0.1:8081/paySuccess
  #同步回调地址 - 成功回调地址
  returnUrl: http://127.0.0.1:8081/paySuccess
  #编码格式
  charset: UTF-8
  #签名类型 - 密钥凭证
  signType: RSA2
  #支付宝的网关地址(注:这里是开发地址,生产和开发不一样)
  gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do
logging:
  level:
    org:
      springframework: info
  file:
    name: /logs/${spring.application.name}.log

代码

支付配置实体类

package com.excesys.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
//对应配置文件里的前缀
@ConfigurationProperties(prefix = "alipay")
public class AliPayConfig {
    private String appId;
    private String privateKey;
    private String alipayPublicKey;
    private String notifyUrl;
    private String returnUrl;
    private String charset;
    private String signType;
    private String gatewayUrl;
}

支付客户端请求实体

package com.excesys.bean;

import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.excesys.config.AliPayConfig;
import com.excesys.pojo.ProductOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AliPay {

    @Autowired
    private AliPayConfig aliPayConfig;

    public String pay(Object obj) throws AlipayApiException {
        //支付网关
        String serverUrl = aliPayConfig.getGatewayUrl();
        // AppId
        String appId = aliPayConfig.getAppId();
        // 用户密钥(私钥),即PKCS8格式RSA2私钥
        String privateKey = aliPayConfig.getPrivateKey();
        //格式化为json格式
        String format = "json";
        //编码
        String charset = aliPayConfig.getCharset();
        //支付宝公钥,即对应Appid下的支付宝公钥
        String alipayPublicKey = aliPayConfig.getAlipayPublicKey();
        //签名方式
        String signType = aliPayConfig.getSignType();
        //页面跳转同步通知页面路径
        String returnUrl = aliPayConfig.getReturnUrl();
        //服务器异步通知页面路径
        String notifyUrl = aliPayConfig.getNotifyUrl();

        //1、获取初始化的AlipayClient
        AlipayClient alipayClient = new DefaultAlipayClient(serverUrl, appId, privateKey, format, charset, alipayPublicKey, signType);
        //2、设置请求参数
        AlipayTradePagePayRequest alipayTradePagePayRequest = new AlipayTradePagePayRequest();
        // 页面跳转同步通知页面路径
        alipayTradePagePayRequest.setReturnUrl(returnUrl);
        alipayTradePagePayRequest.setNotifyUrl(notifyUrl);
        //封装参数(json格式)
        alipayTradePagePayRequest.setBizContent(JSON.toJSONString(obj));

        //3、请求支付宝进行付款,并获取支付结果
        String body = alipayClient.pageExecute(alipayTradePagePayRequest).getBody();
        //放回信息
        return body;
    }

}

Redis配置

package com.excesys.config;

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(factory);
        //redis序列化--key采用String的序列化方式,value采用json的序列化方式
//        template.setKeySerializer(RedisSerializer.string());
//        template.setHashKeySerializer(RedisSerializer.string());
//        template.setValueSerializer(RedisSerializer.json());
//        template.setHashValueSerializer(RedisSerializer.json());
        //Jackson序列化--key采用String的序列化方式,value采用Jackson的序列化方式
//        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//        //序列化包括类型描述 否则反向序列化实体会报错,一律都为JsonObject
//        ObjectMapper mapper = new ObjectMapper();
//        mapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
//        mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(),ObjectMapper.DefaultTyping.NON_FINAL);
//        jackson2JsonRedisSerializer.setObjectMapper(mapper);
//        template.setKeySerializer(RedisSerializer.string());
//        template.setHashKeySerializer(RedisSerializer.string());
//        template.setValueSerializer(jackson2JsonRedisSerializer);
//        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        //FastJson序列化--key采用String的序列化方式,value采用FastJson的序列化方式
        GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

}

redis工具类

package com.excesys.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具类
 */
@Component
public final class RedisUtils {

    private final static Logger log = LoggerFactory.getLogger(RedisUtils.class);

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }
    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *;
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, long time) {
        try {
            redisTemplate.opsForHash().put(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */
    public double hincr(String key, double by) {
        return redisTemplate.opsForHash().increment(key, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */
    public double hdecr(String key, -by);
    }
    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * @param prefix 前缀
     * @param ids    id
     */
    public void delByKeys(String prefix, Set<Long> ids) {
        Set<Object> keys = new HashSet<>();
        for (Long id : ids) {
            keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString()));
        }
        Boolean count = redisTemplate.delete(String.valueOf(keys));
        // 此处提示可自行删除
        log.info("--------------------------------------------");
        log.info("成功删除缓存:" + keys.toString());
        log.info("缓存删除数量:" + count + "个");
        log.info("--------------------------------------------");
    }

    /**
     * @param prefix 前缀
     */
    public void delByKeys(String prefix) {
        Set keys = redisTemplate.keys(prefix + "*");
        long count = redisTemplate.delete(keys);
        // 此处提示可自行删除
        log.info("--------------------------------------------");
        log.info("成功删除缓存:" + keys.toString());
        log.info("缓存删除数量:" + count + "个");
        log.info("--------------------------------------------");
    }

}

订单实体类

package com.excesys.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProductOrder implements Serializable {

    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 商户订单号
     */
    @TableField("out_trade_no")
    private String out_trade_no;
    /**
     * 订单名称
     */
    private String subject;
    /**
     * 付款金额
     */
    @TableField("total_amount")
    private Double total_amount;
    /**
     * 订单状态
     */
    @TableField("order_status")
    private Integer order_status;
    /**
     * 产品编号
     */
    private String product_code = "FAST_INSTANT_TRADE_PAY";
    /**
     * 创建时间
     */
    private LocalDateTime create_time;
}

Dao(数据访问层)

package com.excesys.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.excesys.pojo.ProductOrder;

//这里没有mapper注解是因为启动类已经开启扫描
public interface ProductOrderMapper extends BaseMapper<ProductOrder> {

}

service及其实现(业务逻辑层)

package com.excesys.service;

import com.alipay.api.AlipayApiException;
import com.excesys.pojo.ProductOrder;

public interface AliPayService {
    /**
     * 支付宝支付接口
     */
    String aliPay(ProductOrder order) throws AlipayApiException;
}

package com.excesys.service.impl;

import com.alipay.api.AlipayApiException;
import com.excesys.bean.AliPay;
import com.excesys.pojo.ProductOrder;
import com.excesys.service.AliPayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class AliPayServiceImpl implements AliPayService {

    @Autowired
    private AliPay aliPay;


    @Override
    public String aliPay(ProductOrder order) throws AlipayApiException {
        System.out.println("order----service--:" + order);
        return aliPay.pay(order);
    }
}

Controller(控制层)

package com.excesys.controller;

import cn.hutool.core.util.IdUtil;
import com.alipay.api.AlipayApiException;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.excesys.mapper.ProductOrderMapper;
import com.excesys.pojo.ProductOrder;
import com.excesys.service.AliPayService;
import com.excesys.util.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;

@RestController
public class AliPayController {

    @Autowired
    private AliPayService aliPayService;

    @Autowired
    private RedisUtils redisUtils;

    @Resource
    private ProductOrderMapper productOrderMapper;

    /**
     * 进入支付主页
     *
     * @return
     */
    @GetMapping("/")
    public ModelAndView index() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("testAliPay");
        return mav;
    }

    /**
     * 提交订单
     *
     * @param subject
     * @param totalAmount
     * @return
     * @throws AlipayApiException
     */
    @PostMapping("/order/alipay")
    public String aliPay(String subject, Double totalAmount) throws AlipayApiException {
        System.out.println("--------进入提交支付----------");
        ProductOrder order = new ProductOrder();
        order.setOut_trade_no(IdUtil.getSnowflakeNextIdStr());
        order.setSubject(subject);
        order.setTotal_amount(totalAmount);
        order.setCreate_time(LocalDateTime.now());
        productOrderMapper.insert(order);
        //同时将订单信息存入redis并设置过期时间
        redisUtils.set(order.getOut_trade_no(), order, 60);
        //如果在过期时间内订单未支付则删除redis中的订单信息
        System.out.println("order-----:" + order);
        return aliPayService.aliPay(order);
    }

    /**
     * 支付成功
     *
     * @return
     */
    @GetMapping("/paySuccess")
    public ModelAndView paySuccess(HttpServletRequest request) {
        System.out.println("--------支付成功----------");
        //如果支付成功,订单就要新增到数据库
        //如果取消支付,也要新增订单到数据库,取消订单-状态
        //订单查询能够查出订单状态 信息
        String out_trade_no = request.getParameter("out_trade_no");
        UpdateWrapper uw = new UpdateWrapper();
        uw.eq("out_trade_no", out_trade_no);
        uw.set("order_status", 1);
        int flag = productOrderMapper.update(new ProductOrder(), uw);
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("out_trade_no", out_trade_no);
        modelAndView.setViewName("success");
        System.out.println("支付成功" + flag);
        return modelAndView;
    }

}

mapper(mybatis-plus对单表操作来说xml可以省略)
testAliPay.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>订单支付</title>
</head>
<body>
<table border="1" style="margin: auto;">
    <form th:action="@{/order/alipay}" th:method="post" style="margin: auto;">
        <tr>
            <td>商品名称:</td>
            <td>
                <input type="text" name="subject" value="">
            </td>
        </tr>
        <tr>
            <td>商品价格:</td>
            <td>
                <input type="text" name="totalAmount" value="">
            </td>
        </tr>
        <tr>
            <td colspan="2" style="text-align: center;">
                <input type="submit" value="立即支付">
            </td>
        </tr>
    </form>
</table>
</body>
</html>

success.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>支付成功</title>
</head>
<body>
<h2><span th:text="${ out_trade_no } "></span>支付成功</h2>
</body>
</html>

启动类

package com.excesys;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableAsync
@EnableScheduling
@SpringBootApplication
@MapperScan("com.excesys.mapper")
public class SpringbootPayServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootPayServiceApplication.class, args);
    }

}

订单超时处理的定时任务

package com.excesys.task;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.excesys.mapper.ProductOrderMapper;
import com.excesys.pojo.ProductOrder;
import com.excesys.util.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

@Slf4j
@Component
public class MyOrderTask {

    @Resource
    private ProductOrderMapper productOrderMapper;

    @Autowired
    private RedisUtils redisUtils;

    @Scheduled(cron = "0 0/1 * * * ?")
    public void orderSync() {
        QueryWrapper<ProductOrder> qw = new QueryWrapper<>();
        List<ProductOrder> productOrders = productOrderMapper.selectList(qw);
        for (int i = 0; i < productOrders.size(); i++) {
            //如果在过期时间内订单未支付则删除redis中的订单信息
            long expire = redisUtils.getExpire(productOrders.get(i).getOut_trade_no());
            log.info(productOrders.get(i).getOut_trade_no() + "过期时间" + expire);
            Integer order_status = productOrders.get(i).getOrder_status();
            if (expire <= 0 && order_status == 0) {
                redisUtils.del(productOrders.get(i).getOut_trade_no());
                UpdateWrapper uw = new UpdateWrapper();
                uw.eq("out_trade_no", productOrders.get(i).getOut_trade_no());
                uw.set("order_status", 3);
                int flag = productOrderMapper.update(new ProductOrder(), uw);
                log.info(productOrders.get(i).getOut_trade_no() + "超时订单修改" + flag);
            }
        }

    }

}

效果

启动主启动类访问http://localhost:8081/

在这里插入图片描述


填入订单信息,此时数据库生成一条订单数据状态为0(未支付)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


输入沙箱买家信息和密码并支付

在这里插入图片描述

在这里插入图片描述


支付成功商家加钱买家减钱,数据库此时订单状态变成1(已支付)

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述

在这里插入图片描述


继续访问http://localhost:8081/并填写订单信息,每条订单信息都会放入Redis缓存起来

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


超出一段时间不进行支付操作(作者这里是1分钟),则定时任务会扫描到该订单并先删除Redis数据之后再修改数据库订单状态为3(超时)

在这里插入图片描述


在这里插入图片描述

在这里插入图片描述

写在最后:上面就是一个订单和支付业务,但是是不在高并发状态下的简单实现机制,如果是在高并发状态下大厂的选择会更多(核心是锁),难怪12306要请阿里给他们重构系统,年关将近,祝大家阖家欢乐,万事如意,路漫漫其修远兮,吾将上下而求索!~

相关文章

文章浏览阅读1.3k次。在 Redis 中,键(Keys)是非常重要的概...
文章浏览阅读3.3k次,点赞44次,收藏88次。本篇是对单节点的...
文章浏览阅读8.4k次,点赞8次,收藏18次。Spring Boot 整合R...
文章浏览阅读978次,点赞25次,收藏21次。在Centos上安装Red...
文章浏览阅读1.2k次,点赞21次,收藏22次。Docker-Compose部...
文章浏览阅读2.2k次,点赞59次,收藏38次。合理的JedisPool资...