微服务提供者支付Module模块

建 module

  • 在父工程上单击右键,创建一个新module
  • 在左侧选择maven,然后选择Module SDK的版本号是java1.8
  • name改成cloud-provider-payment8090
  • 点击完成

改POM

支付模块的pom.xml文件如下,我们可以看到没有groupIdh和version,只需要引入artifactId即可

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>cloud2020</artifactId>
  7. <groupId>com.sgy.cloud2020</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>cloud-provider-payment8090</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-web</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>com.alibaba</groupId>
  19. <artifactId>druid</artifactId>
  20. </dependency>
  21. <!--通用mapper依赖包 -->
  22. <dependency>
  23. <groupId>tk.mybatis</groupId>
  24. <artifactId>mapper-spring-boot-starter</artifactId>
  25. </dependency>
  26. <!-- Mysql驱动包 -->
  27. <dependency>
  28. <groupId>mysql</groupId>
  29. <artifactId>mysql-connector-java</artifactId>
  30. </dependency>
  31. <dependency>
  32. <groupId>org.mybatis.spring.boot</groupId>
  33. <artifactId>mybatis-spring-boot-starter</artifactId>
  34. </dependency>
  35. <dependency>
  36. <groupId>org.springframework.boot</groupId>
  37. <artifactId>spring-boot-devtools</artifactId>
  38. <scope>runtime</scope>
  39. <optional>true</optional> <!-- 表示依赖不会传递 -->
  40. </dependency>
  41. <dependency>
  42. <groupId>org.springframework.boot</groupId>
  43. <artifactId>spring-boot-starter-test</artifactId>
  44. </dependency>
  45. <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
  46. <dependency>
  47. <groupId>org.projectlombok</groupId>
  48. <artifactId>lombok</artifactId>
  49. <scope>provided</scope>
  50. </dependency>
  51. </dependencies>
  52. </project>

查看父工程的pom文件

多了一个modules其中包含cloud-provider-payment8090

<modules>
    <module>cloud-provider-payment8090</module>
</modules>

写YML

创建application.yml文件

server:
  port: 8090

spring:
  application:
    name: cloud-provider-payment
  datasource:
    username: blog
    password: 123456
    url: jdbc:mysql://192.168.200.10:3306/cloud?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&serverTimezone=GMT%2B8
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 使用我们自己的druid数据源
    type: com.alibaba.druid.pool.DruidDataSource

    initialSize: 10 #初始化连接个数
    minIdle: 5    #最小连接个数
    maxActive: 500 #最大连接个数
    maxWait: 60000 #最大等待时间
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    #   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500


mybatis:
  # config-location和configuration不能同时配置,否则会抛出异常
  # 一般只配置configuration即可,会自动找到mybatis全局配置文件
  #  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mapper/*Mapper.xml
  # 对应实体类的路径,只能指定具体的包,多个配置可以使用英文逗号隔开
  type-aliases-package: com.sgy.payment
  configuration:
    # Mybatis SQL语句控制台打印
    #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 开启驼峰命名规则
    # 在数据库中字段可以采用驼峰命名规则,mybatis会把 下划线去掉并把下划线后面的首字母认为是大写
    map-underscore-to-camel-case: true

logging:
  level:
    # 注意注意注意 一定要修改成自己的包名
    com.sgy: debug
  file:
    path: log/
    name: log/com.sgy.payment-dev.log
    clean-history-on-start: true
  pattern:
    console: "%d{yyyy-MM-dd} [%thread] %-5level %logger{50} ===> %msg%n"
    file: "%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ===> %msg%n"

主启动

package com.sgy.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * Created by AaronShen on 2020/5/26
 */
@SpringBootApplication
public class PaymentMain8090 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8090.class,args);
    }
}

业务类

建表SQL

CREATE TABLE `payment` (
  `id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `serial` varchar(255) DEFAULT NULL COMMENT '支付流水号',
  `create_time` bigint DEFAULT NULL,
  `update_time` bigint DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

entities

统一返回结果

创建统一返回结果R

package com.sgy.springcloud.entites.result;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * REST API 返回结果
 * 成功返回的代码是200
 * @version 2.0
 * @author 沈光阳
 */
@Data
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
public class R<T> {

    private Boolean success;
    /**
     * 业务错误码
     */
    private Long code;

    /**
     * 描述
     */
    private String msg;

    /**
     * 结果集
     */
    private T data;

    public R ok(String msg, T data) {
        this.setCode(200L);
        this.setMsg(msg);
        this.setData(data);
        this.setSuccess(true);
        return this;
    }

    public R ok(String msg) {
        return this.ok(msg,null);
    }

    public R error(Long code , String msg,T data) {
        this.setCode(code);
        this.setMsg(msg);
        this.setData(data);
        this.setSuccess(false);
        return this;
    }

    public R error(String msg,T data) {
        return this.error(-1L,msg,data);
    }

    public R error(String msg) {
        return this.error(-1L,msg,null);
    }

    public R error(IErrorCode iErrorCode) {
        return this.error(iErrorCode.getCode(),iErrorCode.getMsg(),null);
    }
    public R error(ApiErrorCode apiErrorCode,T data) {
        return this.error(apiErrorCode.getCode(),apiErrorCode.getMsg(),data);
    }

}

统一错误代码接口IErrorCode接口

package com.sgy.springcloud.entites.result;

/**
 * @author 沈光阳
 * @create 2020-04-25-19:31
 */
public interface IErrorCode {
    /**
     * 错误编码
     */
    Long getCode();

    /**
     * 错误描述
     */
    String getMsg();

    /**
     * 是否成功 true成功,false失败
     * @return
     */
    Boolean getSuccess();
}

错误代码枚举

package com.sgy.springcloud.entites.result;


/**
 * 结果类枚举
 */
public enum ApiErrorCode implements IErrorCode {
    SUCCESS(true, 200L, "操作成功"),
    ERROR(false, -1L, "操作失败"),

    ;

    /**
     * 响应是否成功
     */
    private Boolean success;

    /**
     * 响应状态码
     */
    private Long code;

    /**
     * 响应信息
     */
    private String msg;

    ApiErrorCode(boolean success, Long code, String message) {
        this.success = success;
        this.code = code;
        this.msg = message;
    }

    @Override
    public Long getCode() {
        return this.code;
    }

    @Override
    public String getMsg() {
        return this.msg;
    }

    public Boolean getSuccess() {
        return this.success;
    }
}

创建基本实体类

每个实体类基本都有创建时间以及更新时间,因此我可以将这两个字段提取出来

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BaseEntities {
    private Long createTime;
    private Long updateTime;
}

然后其他实体类需要继承该类

创建生成id工具类

在创建每一个实体类之前,我们需要做一件事情,就是创建一个通过雪花算法生成的id工具类

雪花算法工具类

package com.blog.utils;

/**
 * 雪花算法生成分布式唯一数据库主键id
 * 相关文章链接https://juejin.im/post/5deb64f0f265da33d83e64e2#heading-16
 * @author 沈光阳阳
 */
public class SnowFlakeUtil {
    public static void main(String[] args) {
        Long uniqueId = SnowFlakeUtil.nextId();
        System.out.println(uniqueId);
    }

    /**
     * 起始的时间戳:这个时间戳自己随意获取,比如自己代码的时间戳
     */
    private final static long START_TIMESTAMP = 1288834974657L;

    // 每一部分占用的位数
    /**
     * 序列号占用的位数
     */
    private final static long SEQUENCE_BIT = 12;
    /**
     * 机器标识占用的位数
     */
    private final static long MACHINE_BIT = 5;
    /**
     * 数据中心占用的位数
     */
    private final static long DATA_CENTER_BIT = 5;

    // 每一部分的最大值:先进行左移运算,再同-1进行异或运算;异或:相同位置相同结果为0,不同结果为1
    /**
     * 用位运算计算出最大支持的数据中心数量:31
     */
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);

    /**
     * 用位运算计算出最大支持的机器数量:31
     */
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);

    /**
     * 用位运算计算出12位能存储的最大正整数:4095
     */
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    // 每一部分向左的位移
    /**
     * 机器标志较序列号的偏移量
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;

    /**
     * 数据中心较机器标志的偏移量
     */
    private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    /**
     * 时间戳较数据中心的偏移量
     */
    private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;

    /**
     * 数据中心
     */
    private static long datacenterId;
    /**
     * 机器标识
     */
    private static long machineId;
    /**
     * 序列号
     */
    private static long sequence = 0L;
    /**
     * 上一次时间戳
     */
    private static long lastStamp = -1L;

    /**
     * 此处无参构造私有,同时没有给出有参构造,在于避免以下两点问题:
     * 1、私有化避免了通过new的方式进行调用,主要是解决了在for循环中通过new的方式调用产生的id不一定唯一问题问题,因为用于             记录上一次时间戳的lastStmp永远无法得到比对;
     * 2、没有给出有参构造在第一点的基础上考虑了一套分布式系统产生的唯一序列号应该是基于相同的参数
     */
    private SnowFlakeUtil() {

    }

    /**
     * 获得下一个ID(该方法是线程安全的)
     *
     * @return
     */
    public static synchronized long nextId() {
        /** 获取当前时间戳 */
        long currStamp = timeGen();

        /** 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过,这时候应当抛出异常 */
         if (currStamp < lastStamp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }
        /** 如果是同一时间(相同毫秒内)生成的,则进行毫秒内序列 */
        if (currStamp == lastStamp) {
            //相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                /** 阻塞到下一个毫秒,获得新的时间戳赋值给当前时间戳 */
                currStamp = tilNextMillis();
            }
        } else {
            //时间戳改变,毫秒内序列号置为0
            sequence = 0L;
        }
        /** 当前时间戳存档记录,用于下次产生id时对比是否为相同时间戳 */
        lastStamp = currStamp;

        // 移位并通过或运算拼到一起组成64位的ID
        //时间戳部分
        return (currStamp - START_TIMESTAMP) << TIMESTAMP_LEFT
                // 数据中心部分
                | datacenterId << DATA_CENTER_LEFT
                // 机器标识部分
                | machineId << MACHINE_LEFT
                // 序列号部分
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     *
     * @return 当前时间戳
     */
    private static long tilNextMillis() {
        long timestamp = timeGen();
        while (timestamp <= lastStamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private static long timeGen() {
        return System.currentTimeMillis();
    }

}

生成id工具类

package com.blog.utils;

import tk.mybatis.mapper.genid.GenId;

/**
 * @author 沈光阳
 * @create 2020-05-05-13:43
 */
public class GenIdUtil implements GenId<String> {
    @Override
    public String genId(String s, String s1) {
        return String.valueOf(SnowFlakeUtil.nextId());
    }
}

创建payment实体类

package com.sgy.springcloud.entites;

import com.sgy.springcloud.utils.GenIdUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import tk.mybatis.mapper.annotation.KeySql;

import javax.persistence.Id;

/**
 * Created by AaronShen on 2020/5/26
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment extends BaseEntities{
    /**
     * 主键
     */
    @Id
    @KeySql(genId = GenIdUtil.class)
    private String id;

    private String serial;
}

dao

本次我使用的是通用mapper,为了实现一个BaseDaoService我需要进行如下操作

创建MyMapper

一定要在java目录下创建MyMapper,不要在启动类所在的目录以及子目录下创建,否则启动会报错

package tk.mybatis;

import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.special.InsertListMapper;

/**
 * Created by AaronShen on 2020/5/26
 */
public interface MyMapper<T>  extends Mapper<T> , InsertListMapper<T> {
}

创建PaymentDao

package com.sgy.springcloud.dao;

import com.sgy.springcloud.entites.Payment;
import org.apache.ibatis.annotations.Mapper;
import tk.mybatis.MyMapper;

/**
 * Created by AaronShen on 2020/5/26
 */
@Mapper
public interface PaymentDao extends MyMapper<Payment> {
}

service

创建BaseDaoService

package com.sgy.springcloud.service;

import com.github.pagehelper.PageInfo;
import com.sgy.springcloud.entites.BaseEntities;
import tk.mybatis.mapper.entity.Example;

import java.util.List;

/**
 * Created by AaronShen on 2020/5/26
 */
public interface BaseDaoService<T extends BaseEntities,ID>{
    T save(T t);

    int saveAll(List<T> list);

    int delete(T t);

    int deleteById(ID id);

    /**
     * 更新对象,只更新有属性值的属性
     * @param t
     * @param example
     * @return
     */
    int update(T t, Example example);

    T findById(ID id);

    List<T> findAll();

    List<T> findByExample(Example example);

    /**
     * @param example 通过例子对象查询
     * @param pageNum
     * @param pageSize
     * @param orderBy 排序 orderBy="字段 排序规律"; desc为降序,asc为升序
     * @return
     */
    PageInfo<T> page(Example example, int pageNum, int pageSize, String orderBy);
}

创建BaseDaoServiceImpl

package com.sgy.springcloud.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.sgy.springcloud.entites.BaseEntities;
import com.sgy.springcloud.service.BaseDaoService;
import org.springframework.stereotype.Service;
import tk.mybatis.MyMapper;
import tk.mybatis.mapper.entity.Example;

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

/**
 * Created by AaronShen on 2020/5/26
 */
@Service
public class BaseDaoServiceImpl<T extends BaseEntities,ID,D extends MyMapper<T>>
        implements BaseDaoService<T, ID> {
    @Resource
    private D dao;

    @Override
    public T save(T t) {
        t.setCreateTime(System.currentTimeMillis());
        t.setUpdateTime(System.currentTimeMillis());
        int insert = dao.insert(t);
        if (insert != 0) {
            return t;
        }
        return null;
    }

    @Override
    public int delete(T t) {
        return dao.delete(t);
    }

    @Override
    public int deleteById(ID id) {
        return dao.deleteByPrimaryKey(id);
    }

    @Override
    public int update(T t,Example example) {
        t.setUpdateTime(System.currentTimeMillis());
        return dao.updateByExampleSelective(t,example);
    }

    @Override
    public T findById(ID id) {
        return dao.selectByPrimaryKey(id);
    }

    @Override
    public List<T> findAll() {
        List<T> lists = dao.selectAll();
        return lists;
    }

    @Override
    public int saveAll(List<T> list) {
        return dao.insertList(list);
    }

    @Override
    public List<T> findByExample(Example example) {
        return dao.selectByExample(example);
    }

    @Override
    public PageInfo<T> page(Example example , int pageNum, int pageSize, String orderBy) {
        if (orderBy != null && !orderBy.equals("")) {
            PageHelper.startPage(pageNum,pageSize, orderBy);
        } else {
            PageHelper.startPage(pageNum,pageSize);
        }
        List<T> pageList = dao.selectByExample(example);
        return new PageInfo<>(pageList);
    }

}

创建PaymentService

package com.sgy.springcloud.service;

import com.sgy.springcloud.entites.Payment;

/**
 * Created by AaronShen on 2020/5/26
 */
public interface PaymentService extends BaseDaoService<Payment,String> {
}

创建PaymentServiceImpl

package com.sgy.springcloud.service.impl;

import com.sgy.springcloud.dao.PaymentDao;
import com.sgy.springcloud.entites.Payment;
import com.sgy.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;

/**
 * Created by AaronShen on 2020/5/26
 */
@Service
public class PaymentServiceImpl extends BaseDaoServiceImpl<Payment,String,PaymentDao>
        implements PaymentService {
}

controller

package com.sgy.springcloud.controller;

import com.sgy.springcloud.entites.Payment;
import com.sgy.springcloud.entites.result.R;
import com.sgy.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * Created by AaronShen on 2020/5/26
 */
@Slf4j
@RestController
public class PaymentController {
    @Resource
    PaymentService paymentService;

    /**
     * 创建一个订单
     * @return
     */
    @PostMapping(value = "/payment/create")
    public R create(@RequestBody Payment payment) {
        Payment savePayment = paymentService.save(payment);
        if (savePayment != null) {
            log.debug("成功创建订单 {}",savePayment);
            return new R().ok("成功创建订单",savePayment);
        }
        log.error("创建订单失败 {}",payment);
        return new R().error("创建订单失败");
    }

    /**
     * 根据订单id,查询订单
     */
    @GetMapping(value = "/payment/find/{id}")
    public R find(@PathVariable(value = "id",required = true) String id) {
        Payment payment = paymentService.findById(id);
        if (payment != null) {
            log.debug("查询订单成功",payment);
            return new R().ok("查询成功",payment);
        }
        log.info("查询订单失败",payment);
        return new R().error("查询失败",payment);
    }
}

使用postman进行测试

测试创建订单

  因为创建订单编号上我,使用了@RequestBody 即要求请求体,必须是json提交形式,而不能是表单提交,表单提交的形式如下

http://ip:端口/payment/create?id=3333&serial=ccccc

修改postman为json提交形式

请求头:Content-Type: application/json

在如下区域进行编写json,然后提交

1610803427790.png

查询订单

1610803427818.png