yupeng
2025-02-08 7a7bf42d925f05758683ac053eb2a0f6f0c54834
feat:新增订单/账单关联表、支付订单表、新增小程序创建订单接口
10个文件已修改
25个文件已添加
1103 ■■■■■ 已修改文件
generator/src/main/java/com/ruoyi/system/controller/TOrderBillController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/java/com/ruoyi/system/controller/TPayOrderController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/java/com/ruoyi/system/mapper/TOrderBillMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/java/com/ruoyi/system/mapper/TPayOrderMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/java/com/ruoyi/system/model/TOrderBill.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/java/com/ruoyi/system/model/TPayOrder.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/java/com/ruoyi/system/service/TOrderBillService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/java/com/ruoyi/system/service/TPayOrderService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/java/com/ruoyi/system/service/impl/TOrderBillServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/java/com/ruoyi/system/service/impl/TPayOrderServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/resources/mapping/TOrderBillMapper.xml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/resources/mapping/TPayOrderMapper.xml 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/test/java/com/xizang/CodeGeneratorTests.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TBillController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/PayController.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/constant/AmountConstant.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/constant/WxConstant.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/OrderNos.java 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/MakeOrderDto.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/MakeOrderResp.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/TBillDto.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TBillMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TOrderBillMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TPayOrderMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/model/TOrderBill.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/model/TPayOrder.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/query/TBillQuery.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/TBillService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/TOrderBillService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/TPayOrderService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBillServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TOrderBillServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TPayOrderServiceImpl.java 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TBillMapper.xml 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
generator/src/main/java/com/ruoyi/system/controller/TOrderBillController.java
New file
@@ -0,0 +1,21 @@
package com.ruoyi.system.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * 订单表与账单的关联表 前端控制器
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
@RestController
@RequestMapping("/t-order-bill")
public class TOrderBillController {
}
generator/src/main/java/com/ruoyi/system/controller/TPayOrderController.java
New file
@@ -0,0 +1,21 @@
package com.ruoyi.system.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * 支付订单表 前端控制器
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
@RestController
@RequestMapping("/t-pay-order")
public class TPayOrderController {
}
generator/src/main/java/com/ruoyi/system/mapper/TOrderBillMapper.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.system.mapper;
import com.ruoyi.system.model.TOrderBill;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * <p>
 * 订单表与账单的关联表 Mapper 接口
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
public interface TOrderBillMapper extends BaseMapper<TOrderBill> {
}
generator/src/main/java/com/ruoyi/system/mapper/TPayOrderMapper.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.system.mapper;
import com.ruoyi.system.model.TPayOrder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * <p>
 * 支付订单表 Mapper 接口
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
public interface TPayOrderMapper extends BaseMapper<TPayOrder> {
}
generator/src/main/java/com/ruoyi/system/model/TOrderBill.java
New file
@@ -0,0 +1,47 @@
package com.ruoyi.system.model;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * <p>
 * 订单表与账单的关联表
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_order_bill")
@ApiModel(value="TOrderBill对象", description="订单表与账单的关联表")
public class TOrderBill implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId("id")
    private String id;
    @ApiModelProperty(value = "订单号")
    @TableField("order_no")
    private String orderNo;
    @ApiModelProperty(value = "账单编号")
    @TableField("bill_id")
    private String billId;
    @TableField("create_time")
    private LocalDateTime createTime;
    @TableField("update_time")
    private LocalDateTime updateTime;
}
generator/src/main/java/com/ruoyi/system/model/TPayOrder.java
New file
@@ -0,0 +1,86 @@
package com.ruoyi.system.model;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * <p>
 * 支付订单表
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_pay_order")
@ApiModel(value="TPayOrder对象", description="支付订单表")
public class TPayOrder implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "订单号")
    @TableId("id")
    private String id;
    @ApiModelProperty(value = "发起支付的用户ID")
    @TableField("user_id")
    private String userId;
    @ApiModelProperty(value = "用户名称")
    @TableField("user_name")
    private String userName;
    @ApiModelProperty(value = "用户微信openid或支付宝userid")
    @TableField("open_id")
    private String openId;
    @ApiModelProperty(value = "用户手机号码")
    @TableField("phone")
    private String phone;
    @ApiModelProperty(value = "订单金额")
    @TableField("amount")
    private Long amount;
    @ApiModelProperty(value = "实付金额")
    @TableField("act_pay_amount")
    private Long actPayAmount;
    @ApiModelProperty(value = "支付时间")
    @TableField("pay_time")
    private LocalDateTime payTime;
    @ApiModelProperty(value = "支付方式")
    @TableField("pay_type")
    private String payType;
    @ApiModelProperty(value = "支付的回调信息")
    @TableField("pay_info")
    private String payInfo;
    @ApiModelProperty(value = "银行的支付流水编号")
    @TableField("pay_no")
    private String payNo;
    @ApiModelProperty(value = "支付的回调时间")
    @TableField("callback_time")
    private LocalDateTime callbackTime;
    @ApiModelProperty(value = "订单创建时间")
    @TableField("create_time")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "订单更新时间")
    @TableField("update_time")
    private LocalDateTime updateTime;
}
generator/src/main/java/com/ruoyi/system/service/TOrderBillService.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.system.service;
import com.ruoyi.system.model.TOrderBill;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * 订单表与账单的关联表 服务类
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
public interface TOrderBillService extends IService<TOrderBill> {
}
generator/src/main/java/com/ruoyi/system/service/TPayOrderService.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.system.service;
import com.ruoyi.system.model.TPayOrder;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * 支付订单表 服务类
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
public interface TPayOrderService extends IService<TPayOrder> {
}
generator/src/main/java/com/ruoyi/system/service/impl/TOrderBillServiceImpl.java
New file
@@ -0,0 +1,20 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.system.model.TOrderBill;
import com.ruoyi.system.mapper.TOrderBillMapper;
import com.ruoyi.system.service.TOrderBillService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * 订单表与账单的关联表 服务实现类
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
@Service
public class TOrderBillServiceImpl extends ServiceImpl<TOrderBillMapper, TOrderBill> implements TOrderBillService {
}
generator/src/main/java/com/ruoyi/system/service/impl/TPayOrderServiceImpl.java
New file
@@ -0,0 +1,20 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.system.model.TPayOrder;
import com.ruoyi.system.mapper.TPayOrderMapper;
import com.ruoyi.system.service.TPayOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * 支付订单表 服务实现类
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
@Service
public class TPayOrderServiceImpl extends ServiceImpl<TPayOrderMapper, TPayOrder> implements TPayOrderService {
}
generator/src/main/resources/mapping/TOrderBillMapper.xml
New file
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.TOrderBillMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.system.model.TOrderBill">
        <id column="id" property="id" />
        <result column="order_no" property="orderNo" />
        <result column="bill_id" property="billId" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
    </resultMap>
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, order_no, bill_id, create_time, update_time
    </sql>
</mapper>
generator/src/main/resources/mapping/TPayOrderMapper.xml
New file
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.TPayOrderMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.system.model.TPayOrder">
        <id column="id" property="id" />
        <result column="user_id" property="userId" />
        <result column="user_name" property="userName" />
        <result column="open_id" property="openId" />
        <result column="phone" property="phone" />
        <result column="amount" property="amount" />
        <result column="act_pay_amount" property="actPayAmount" />
        <result column="pay_time" property="payTime" />
        <result column="pay_type" property="payType" />
        <result column="pay_info" property="payInfo" />
        <result column="pay_no" property="payNo" />
        <result column="callback_time" property="callbackTime" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
    </resultMap>
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, user_id, user_name, open_id, phone, amount, act_pay_amount, pay_time, pay_type, pay_info, pay_no, callback_time, create_time, update_time
    </sql>
</mapper>
generator/src/test/java/com/xizang/CodeGeneratorTests.java
@@ -5,11 +5,13 @@
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.LikeTable;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@@ -35,7 +37,7 @@
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = "F:\\workSpace\\xizang\\generator";
        String projectPath = "D:\\workspaces\\Project\\company\\changyun\\xizang\\xizang\\generator";
        gc.setOutputDir(projectPath + "/src/main/java")
                .setAuthor("xiaochen")
                .setMapperName("%sMapper")
@@ -144,7 +146,7 @@
//         strategy.setTablePrefix(pc.getModuleName() + "");
//        strategy.setLikeTable(new LikeTable("room"));
        //strategy.setLikeTable(new LikeTable("member"));
        strategy.setLikeTable(new LikeTable("t_"));// 生成表名
        strategy.setLikeTable(new LikeTable("order"));// 生成表名
//        strategy.setLikeTable(new LikeTable("t_hotel"));// 生成表名
//        strategy.setLikeTable(new LikeTable("t_scan_message"));// 生成表名
//        strategy.setNotLikeTable(new LikeTable("hotel_info"));// 不生成表名
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TBillController.java
@@ -3,6 +3,7 @@
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.dto.TBillDto;
import com.ruoyi.system.model.TBill;
import com.ruoyi.system.query.TBillQuery;
import com.ruoyi.system.service.TBillService;
@@ -28,8 +29,8 @@
    TBillService tBillService;
    @PostMapping("list")
    public R<PageInfo<TBill>> list(@RequestBody TBillQuery query){
        PageInfo<TBill> pageInfo = tBillService.queryPage(query);
    public R<PageInfo<TBillDto>> list(@RequestBody TBillQuery query){
        PageInfo<TBillDto> pageInfo = tBillService.queryPage(query);
        return R.ok(pageInfo);
    }
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/PayController.java
@@ -1,7 +1,15 @@
package com.ruoyi.web.controller.api;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.dto.MakeOrderDto;
import com.ruoyi.system.dto.MakeOrderResp;
import com.ruoyi.system.service.TPayOrderService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -11,12 +19,14 @@
    @Autowired
    TPayOrderService tPayOrderService;
    @ApiOperation(value = "创建支付订单")
    @PostMapping("makeOrder")
    public AjaxResult makeOrder(){
        return null;
    public R<MakeOrderResp> makeOrder(@Validated @RequestBody MakeOrderDto dto){
        MakeOrderResp resp = tPayOrderService.makeOrder(dto);
        return R.ok(resp);
    }
ruoyi-common/src/main/java/com/ruoyi/common/constant/AmountConstant.java
New file
@@ -0,0 +1,15 @@
package com.ruoyi.common.constant;
import java.math.BigDecimal;
public class AmountConstant {
    public static final BigDecimal b100 = new BigDecimal("100");
    public static final BigDecimal b1000 = new BigDecimal("1000");
    public static final BigDecimal b001 = new BigDecimal("0.01");
}
ruoyi-common/src/main/java/com/ruoyi/common/constant/WxConstant.java
@@ -16,4 +16,7 @@
     * 高德地图坐标转换
     */
    public static final String ADDRESS_CONVERT_TO_COORDINATE = "https://restapi.amap.com/v3/geocode/geo?key=KEY&address=ADDRESS";
}
ruoyi-common/src/main/java/com/ruoyi/common/utils/OrderNos.java
New file
@@ -0,0 +1,164 @@
package com.ruoyi.common.utils;
import com.ruoyi.common.utils.ip.IpUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.atomic.AtomicLong;
public class OrderNos {
    public static final char PADCHAR = '0';
    /**
     * 长度24位全球唯一标识
     */
    /**
     * 统一不重复内部订单号
     *
     * @param systemCode
     *            4字节系统编码
     * @return 24字节不重复编码
     */
    public static String oid(String systemCode) {
        StringBuilder sb = new StringBuilder();
        sb.append(padding(systemCode, 4));
        sb.append(Did.getInstance().getId(20));
        return sb.toString();
    }
    public static String oid() {
        return oid("O001");
    }
    /**
     * 获取分布式Id
     *
     * @return 默认20字符长度的数字不重复编码
     */
    public static String getDid() {
        return Did.getInstance().getId();
    }
    public static String getDid(int size) {
        return Did.getInstance().getId(size);
    }
    public static String getDid(String prefix) {
        return prefix + Did.getInstance().getId();
    }
    private static String padding(String text, int size) {
        String txt = StringUtils.trimToEmpty(text);
        if (StringUtils.length(txt) > size) {
            txt = StringUtils.substring(txt, StringUtils.length(txt) - size);
        }
        return StringUtils.leftPad(txt, size, PADCHAR);
    }
    /**
     * ID生成器
     *
     * 20字符长度 yyMMddHHmmss+3位本机IP末三位+5位随机数字
     *
     * @author zhangpu
     *
     */
    public static class Did {
        private static final Logger logger = LoggerFactory.getLogger(OrderNos.class);
        private static final int MIN_LENGTH = 19;
        private static final int SEQU_MAX = 99999;
        private static final int SEQU_19_MAX = 9999;
        private volatile static long BASE = 0;
        private AtomicLong sequence = new AtomicLong(0);
        private String nodeFlag;
        private Object nodeFlagLock = new Object();
        private static Did did = new Did();
        public static Did getInstance() {
            return did;
        }
        /**
         * 生产新Id
         *
         * @return
         */
        public  String getId() {
            return getId(20);
        }
        public  String getId(int size) {
            if (size < MIN_LENGTH) {
                throw new RuntimeException("did最小长度为" + MIN_LENGTH);
            }
            StringBuilder sb = new StringBuilder();
            sb.append(DateUtils.dateTimeNow("yyMMddHHmmss"));
            sb.append(getNodeFlag());
            sb.append(getSequ(size-15));
            return sb.toString();
        }
        public synchronized String getSequ(int size) {
            long timeCount = System.currentTimeMillis() / 1000;
            while (true) {
                long now = sequence.get();
                if (timeCount > now) {
                    // 已经过了本秒,则设置新的值
                    if (sequence.compareAndSet(now, timeCount)) {
                        break;
                    }
                } else {
                    if (sequence.compareAndSet(now, now + 1)) {
                        timeCount = now + 1;
                        break;
                    }
                }
            }
            int sn = (int) (timeCount % (size>=5?SEQU_MAX:SEQU_19_MAX));
            return StringUtils.leftPad(String.valueOf(sn), size, '0');
        }
        private  String  getNodeFlag(){
            if (this.nodeFlag == null) {
                synchronized (Did.class){
                    if (this.nodeFlag==null){
                        this.nodeFlag = generateNodeFlag();
                    }
                }
            }
            return this.nodeFlag;
        }
        /**
         * 简单节点编码
         *
         * 逻辑:Ip地址后三位,便于快速知道是哪个节点
         *
         * @return
         */
        private String generateNodeFlag() {
            String ipPostfix = null;
            try {
                String ip = IpUtils.getFirstNoLoopbackIPV4Address();
                ipPostfix = StringUtils.substringAfterLast(ip, ".");
            } catch (Exception e) {
                logger.warn("生产DID要素本机IP获取失败:" + e.getMessage());
            }
            return StringUtils.leftPad(ipPostfix, 3, "0");
        }
        private Did() {
            super();
        }
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java
@@ -1,16 +1,20 @@
package com.ruoyi.common.utils.ip;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.net.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
/**
 * 获取IP方法
 * 
 * @author ruoyi
 */
@Slf4j
public class IpUtils
{
    public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
@@ -379,4 +383,72 @@
        }
        return false;
    }
    private static final String LOOP_BACK = "127.0.0.1";
    private static String firstNoLoopbackIPV4Address = null;
    private static Collection<InetAddress> allHostIPV4Address = null;
    /**
     * 获取ipv4地址,如果有多个网卡的情况,获取第一个非loopback ip地址
     *
     * @return
     */
    public static String getFirstNoLoopbackIPV4Address() {
        if (firstNoLoopbackIPV4Address != null) {
            return firstNoLoopbackIPV4Address;
        }
        Collection<String> allNoLoopbackAddresses = null;
        try {
            allNoLoopbackAddresses = getAllNoLoopbackIPV4Addresses();
        } catch (Exception e) {
            log.error("获取ip失败", e);
            return LOOP_BACK;
        }
        if (allNoLoopbackAddresses.isEmpty()) {
            return LOOP_BACK;
        }
        return firstNoLoopbackIPV4Address = allNoLoopbackAddresses.iterator().next();
    }
    public static Collection<String> getAllNoLoopbackIPV4Addresses() {
        Collection<String> noLoopbackAddresses = new ArrayList<String>();
        Collection<InetAddress> allInetAddresses = getAllHostIPV4Address();
        for (InetAddress address : allInetAddresses) {
            if (!address.isLoopbackAddress()) {
                noLoopbackAddresses.add(address.getHostAddress());
            }
        }
        return noLoopbackAddresses;
    }
    public static Collection<InetAddress> getAllHostIPV4Address() {
        if (allHostIPV4Address == null) {
            try {
                Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
                Collection<InetAddress> addresses = new ArrayList<InetAddress>();
                while (networkInterfaces.hasMoreElements()) {
                    NetworkInterface networkInterface = networkInterfaces.nextElement();
                    Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
                    while (inetAddresses.hasMoreElements()) {
                        InetAddress inetAddress = inetAddresses.nextElement();
                        if (inetAddress instanceof Inet4Address) {
                            addresses.add(inetAddress);
                        }
                    }
                }
                allHostIPV4Address = addresses;
            } catch (SocketException e) {
                log.error("获取ip地址失败", e);
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        return allHostIPV4Address;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/dto/MakeOrderDto.java
New file
@@ -0,0 +1,38 @@
package com.ruoyi.system.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
@Data
@ApiModel(value = "使用账单创建订单请求")
public class MakeOrderDto implements Serializable {
    /**
     * 用户ID
     */
    @ApiModelProperty(value = "用户ID")
    @NotBlank(message = "用户ID不能为空")
    private String userId;
    /**
     * 用户openid
     */
    @ApiModelProperty(value = "用户微信或支付宝openid")
    private String openId;
    @ApiModelProperty(value = "支付金额,单位:分")
    @NotNull(message = "用户ID不能为空")
    private Long amount;
    @ApiModelProperty(value = "账单ID列表",notes = "如果只有1个账单ID,金额可以小于账单金额,进行部分缴费;如果是多个账单,将会核对金额,未交费金额需要与缴费金额一致")
    @NotEmpty(message = "用户ID不能为空")
    private List<String> billIds;
}
ruoyi-system/src/main/java/com/ruoyi/system/dto/MakeOrderResp.java
New file
@@ -0,0 +1,23 @@
package com.ruoyi.system.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(value = "使用账单创建订单请求")
public class MakeOrderResp implements Serializable {
    /**
     * 订单编号
     */
    @ApiModelProperty("订单编号")
    private String orderNo;
    @ApiModelProperty("金额,分")
    private Long amount;
    @ApiModelProperty("微信小程序支付跳转链接,用于生成二维码")
    private String appletUrl;
}
ruoyi-system/src/main/java/com/ruoyi/system/dto/TBillDto.java
New file
@@ -0,0 +1,17 @@
package com.ruoyi.system.dto;
import com.ruoyi.system.model.TBill;
import lombok.Data;
import java.util.List;
@Data
public class TBillDto extends TBill {
    private String residentName;
    private String phone;
    private String account;
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TBillMapper.java
@@ -1,7 +1,12 @@
package com.ruoyi.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.dto.TBillDto;
import com.ruoyi.system.model.TBill;
import com.ruoyi.system.query.TBillQuery;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
 * <p>
@@ -13,4 +18,6 @@
 */
public interface TBillMapper extends BaseMapper<TBill> {
    PageInfo<TBillDto> page(@Param("pageInfo") PageInfo<TBill> pageInfo, @Param("query") TBillQuery query);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TOrderBillMapper.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.system.model.TOrderBill;
/**
 * <p>
 * 订单表与账单的关联表 Mapper 接口
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
public interface TOrderBillMapper extends BaseMapper<TOrderBill> {
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TPayOrderMapper.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.system.model.TPayOrder;
/**
 * <p>
 * 支付订单表 Mapper 接口
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
public interface TPayOrderMapper extends BaseMapper<TPayOrder> {
}
ruoyi-system/src/main/java/com/ruoyi/system/model/TOrderBill.java
New file
@@ -0,0 +1,54 @@
package com.ruoyi.system.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * 订单表与账单的关联表
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@TableName("t_order_bill")
@ApiModel(value="TOrderBill对象", description="订单表与账单的关联表")
public class TOrderBill implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private String id;
    @ApiModelProperty(value = "订单号")
    @TableField("order_no")
    private String orderNo;
    @ApiModelProperty(value = "账单编号")
    @TableField("bill_id")
    private String billId;
    @TableField("create_time")
    private LocalDateTime createTime;
    @TableField("update_time")
    private LocalDateTime updateTime;
    public TOrderBill(String orderNo, String billId) {
        this.orderNo = orderNo;
        this.billId = billId;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/model/TPayOrder.java
New file
@@ -0,0 +1,88 @@
package com.ruoyi.system.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * 支付订单表
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_pay_order")
@ApiModel(value="TPayOrder对象", description="支付订单表")
public class TPayOrder implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "订单号")
    @TableId("id")
    private String id;
    @ApiModelProperty(value = "发起支付的用户ID")
    @TableField("user_id")
    private String userId;
    @ApiModelProperty(value = "用户名称")
    @TableField("user_name")
    private String userName;
    @ApiModelProperty(value = "用户微信openid或支付宝userid")
    @TableField("open_id")
    private String openId;
    @ApiModelProperty(value = "用户手机号码")
    @TableField("phone")
    private String phone;
    @ApiModelProperty(value = "订单金额")
    @TableField("amount")
    private Long amount;
    @ApiModelProperty(value = "实付金额")
    @TableField("act_pay_amount")
    private Long actPayAmount;
    @ApiModelProperty(value = "支付时间")
    @TableField("pay_time")
    private LocalDateTime payTime;
    @ApiModelProperty(value = "支付方式")
    @TableField("pay_type")
    private String payType;
    @ApiModelProperty(value = "支付的回调信息")
    @TableField("pay_info")
    private String payInfo;
    @ApiModelProperty(value = "银行的支付流水编号")
    @TableField("pay_no")
    private String payNo;
    @ApiModelProperty(value = "支付的回调时间")
    @TableField("callback_time")
    private LocalDateTime callbackTime;
    @ApiModelProperty(value = "订单创建时间")
    @TableField("create_time")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "订单更新时间")
    @TableField("update_time")
    private LocalDateTime updateTime;
}
ruoyi-system/src/main/java/com/ruoyi/system/query/TBillQuery.java
@@ -10,6 +10,12 @@
     */
    private Integer payFeesStatus;
    private String phone;
    private String residentName;
    private String contractNumber;
}
ruoyi-system/src/main/java/com/ruoyi/system/service/TBillService.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.dto.TBillDto;
import com.ruoyi.system.model.TBill;
import com.ruoyi.system.query.TBillQuery;
@@ -15,6 +16,6 @@
 */
public interface TBillService extends IService<TBill> {
    PageInfo<TBill> queryPage(TBillQuery query);
    PageInfo<TBillDto> queryPage(TBillQuery query);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/TOrderBillService.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.system.model.TOrderBill;
/**
 * <p>
 * 订单表与账单的关联表 服务类
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
public interface TOrderBillService extends IService<TOrderBill> {
}
ruoyi-system/src/main/java/com/ruoyi/system/service/TPayOrderService.java
New file
@@ -0,0 +1,19 @@
package com.ruoyi.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.system.dto.MakeOrderDto;
import com.ruoyi.system.dto.MakeOrderResp;
import com.ruoyi.system.model.TPayOrder;
/**
 * <p>
 * 支付订单表 服务类
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
public interface TPayOrderService extends IService<TPayOrder> {
    MakeOrderResp makeOrder(MakeOrderDto dto);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBillServiceImpl.java
@@ -1,16 +1,22 @@
package com.ruoyi.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.system.dto.TBillDto;
import com.ruoyi.system.mapper.TBillMapper;
import com.ruoyi.system.model.TBill;
import com.ruoyi.system.model.TContract;
import com.ruoyi.system.model.THouse;
import com.ruoyi.system.query.TBillQuery;
import com.ruoyi.system.service.TBillService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.jsonwebtoken.lang.Assert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
/**
 * <p>
@@ -29,8 +35,10 @@
    @Autowired
    TBillMapper tBillMapper;
    public PageInfo<TBill> queryPage(TBillQuery query){
        return null;
    public PageInfo<TBillDto> queryPage(TBillQuery query){
        PageInfo<TBill> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        PageInfo<TBillDto> info = tBillMapper.page(pageInfo, query);
        return info;
    }
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TOrderBillServiceImpl.java
New file
@@ -0,0 +1,20 @@
package com.ruoyi.system.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.system.mapper.TOrderBillMapper;
import com.ruoyi.system.model.TOrderBill;
import com.ruoyi.system.service.TOrderBillService;
import org.springframework.stereotype.Service;
/**
 * <p>
 * 订单表与账单的关联表 服务实现类
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
@Service
public class TOrderBillServiceImpl extends ServiceImpl<TOrderBillMapper, TOrderBill> implements TOrderBillService {
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TPayOrderServiceImpl.java
New file
@@ -0,0 +1,130 @@
package com.ruoyi.system.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.constant.AmountConstant;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.OrderNos;
import com.ruoyi.system.dto.MakeOrderDto;
import com.ruoyi.system.dto.MakeOrderResp;
import com.ruoyi.system.mapper.TPayOrderMapper;
import com.ruoyi.system.model.TBill;
import com.ruoyi.system.model.TOrderBill;
import com.ruoyi.system.model.TPayOrder;
import com.ruoyi.system.model.TTenant;
import com.ruoyi.system.service.TBillService;
import com.ruoyi.system.service.TOrderBillService;
import com.ruoyi.system.service.TPayOrderService;
import com.ruoyi.system.service.TTenantService;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
 * <p>
 * 支付订单表 服务实现类
 * </p>
 *
 * @author xiaochen
 * @since 2025-02-07
 */
@Service
public class TPayOrderServiceImpl extends ServiceImpl<TPayOrderMapper, TPayOrder> implements TPayOrderService {
    @Autowired
    TBillService billService;
    @Autowired
    TTenantService tTenantService;
    @Autowired
    TOrderBillService tOrderBillService;
    public long caculateRentFee(TBill bill){
        return bill.getPayableFeesMoney().add(bill.getPayableFeesPenalty())
                .subtract(bill.getActualMoney())
                .multiply(AmountConstant.b100)
                .setScale(0, RoundingMode.HALF_DOWN)
                .longValue();
    }
    public String createAppletUrl(String orderNo){
        return ""+orderNo;
    }
    @Override
    public MakeOrderResp makeOrder(MakeOrderDto dto) {
        TTenant user = tTenantService.getById(dto.getUserId());
        if (user==null){
            throw new ServiceException("租户不存在");
        }
        MakeOrderResp resp = new MakeOrderResp();
        if (dto.getBillIds().size()==1){
            TBill bill = billService.getById(dto.getBillIds().get(0));
            if (bill == null) {
                throw new ServiceException("账单不存在");
            }
            //计算欠费金额:租金+违约金-实收金额
            long rent = caculateRentFee(bill);
            if (rent==0){
                throw new ServiceException("该订单已缴费");
            }
            if (rent>dto.getAmount()){
                throw new ServiceException("支付金额超过了账单欠费金额");
            }
            TPayOrder order = new TPayOrder();
            order.setId(OrderNos.getDid(32));
            order.setAmount(dto.getAmount());
            order.setUserId(user.getId());
            order.setPhone(user.getPhone());
            order.setUserName(user.getResidentName());
            save(order);
            TOrderBill tOrderBill = new TOrderBill();
            tOrderBill.setBillId(bill.getId());
            tOrderBill.setOrderNo(order.getId());
            tOrderBillService.save(tOrderBill);
            resp.setAmount(dto.getAmount());
            resp.setOrderNo(order.getId());
            resp.setAppletUrl(createAppletUrl(order.getId()));
            return resp;
        }
        List<TOrderBill> orderBills = new ArrayList<>();
        String orderNo = OrderNos.getDid(32);
        List<TBill> bills = dto.getBillIds().stream().map(id -> {
            TBill bill = billService.getById(id);
            if (bill == null) {
                throw new ServiceException("billId:" + id + "不存在");
            }
            orderBills.add(new TOrderBill(orderNo,bill.getId()));
            return bill;
        }).collect(Collectors.toList());
        long sumRent = bills.stream().mapToLong((bill) -> caculateRentFee(bill)).sum();
        if (sumRent==0){
            throw new ServiceException("账单已缴费");
        }
        if (dto.getAmount()>sumRent){
            throw new ServiceException("支付金额超过所选账单欠费金额");
        }
        TPayOrder order = new TPayOrder();
        order.setId(orderNo);
        order.setAmount(dto.getAmount());
        order.setUserId(user.getId());
        order.setPhone(user.getPhone());
        order.setUserName(user.getResidentName());
        save(order);
        tOrderBillService.saveBatch(orderBills);
        resp.setAmount(dto.getAmount());
        resp.setOrderNo(orderNo);
        resp.setAppletUrl(createAppletUrl(orderNo));
        return resp;
    }
}
ruoyi-system/src/main/resources/mapper/system/TBillMapper.xml
@@ -32,4 +32,29 @@
        id, contract_id, payable_fees_money, payable_fees_time, pay_fees_status, pay_fees_money, pay_fees_time, pay_fees_type, bill_type, over_days, payable_fees_penalty, start_time, end_time, bank_serial_number, actual_money, voucher, create_time, update_time, create_by, update_by, disabled
    </sql>
    <select id="page" resultType="com.ruoyi.system.dto.TBillDto">
        SELECT
            b.*,
            t.resident_name as residentName,
            t.phone,
            t.account
        FROM
            t_bill b
        LEFT JOIN t_contract c ON c.contract_number = b.contract_number
        LEFT JOIN t_tenant t ON t.id = c.tenant_id
        <where>
            <if test="query.payFeesStatus != null">
                and b.pay_fees_status = #{query.payFeesStatus}
            </if>
            <if test="query.phone != null and query.phone !=''">
                and t.phone = #{query.phone}
            </if>
            <if test="query.residentName != null and query.residentName !=''">
                and t.resident_name like concat('%',#{query.residentName},'%')
            </if>
            <if test="query.contractNumber != null and query.contractNumber !=''">
                and b.contract_number = #{contractNumber}
            </if>
        </where>
    </select>
</mapper>