无关风月
4 天以前 c72910d2b90f74d23e770717d80921b4fd064d48
新增用户提现
5个文件已修改
13个文件已添加
1758 ■■■■■ 已修改文件
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/AppUserBank.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/UserPoint.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/UserWithdraw.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/AppUserBankController.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/AppUserController.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/UserWithdrawController.java 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/dto/SaveWithdrawalAccount.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/mapper/UserWithdrawMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/IUserWithdrawService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/UserWithdrawServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/BankCode.java 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/HttpRequester.java 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/HttpRespons.java 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/UUIDUtil.java 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/withdraw/HttpUtilWithdraw.java 492 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/withdraw/WithdrawCallBackDTO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/UserWithdrawMapper.xml 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/AppUserBank.java
@@ -40,4 +40,16 @@
    @TableField("bank_number")
    @ApiModelProperty(value = "银行卡号", required = true)
    private String bankNumber;
    /**
     * 持卡人名称
     */
    @TableField("user_name")
    @ApiModelProperty(value = "持卡人名称", required = true)
    private String userName;
    @ApiModelProperty("账户类型(对私账户201,对公账户204)")
    @TableField("receiverAccountType")
    private Integer receiverAccountType;
    @ApiModelProperty("收款方开户行编号")
    @TableField("receiverBankChannelNo")
    private String receiverBankChannelNo;
}
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/UserPoint.java
@@ -39,7 +39,7 @@
    private Long id;
    @ApiModelProperty(value = "变动类型(4=兑换商品,12=他人赠送,13=赠与他人,16=取消订单,17=充值 )")
    @Excel(name = "变动类型", readConverterExp = "4=兑换商品,12=他人赠送,13=赠与他人,16=取消订单,17=充值 ")
    @Excel(name = "变动类型", readConverterExp = "4=兑换商品,12=他人赠送,13=赠与他人,16=取消订单,17=充值 18=提现 19=提现失败回退积分 ")
    @TableField("type")
    private Integer type;
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/model/UserWithdraw.java
New file
@@ -0,0 +1,110 @@
package com.ruoyi.account.api.model;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <p>
 * 用户提现记录
 * </p>
 *
 * @author lingma
 * @since 2025-04-05
 */
@Data
@TableName("t_user_withdraw")
@ApiModel(value = "UserWithdraw对象", description = "用户提现记录")
public class UserWithdraw implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "主键")
    @TableId("id")
    private Long id;
    @ApiModelProperty(value = "uid")
    @TableField(exist = false)
    private String uid;
    @ApiModelProperty(value = "用户id")
    @TableField("app_user_id")
    private Long appUserId;
    @ApiModelProperty(value = "提现金额")
    @TableField("money")
    private BigDecimal money;
    @ApiModelProperty(value = "消耗积分")
    @TableField("integral")
    private Integer integral;
    @ApiModelProperty(value = "审核状态(0=待审核,1=审核通过,2=审核失败)")
    @TableField("audit_status")
    private Integer auditStatus;
    @ApiModelProperty(value = "审核人id")
    @TableField("audit_user_id")
    private Long auditUserId;
    @ApiModelProperty(value = "审核时间")
    @TableField("audit_time")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime auditTime;
    @ApiModelProperty(value = "审核结果")
    @TableField("audit_msg")
    private String auditMsg;
    @ApiModelProperty(value = "状态(1=申请中,2=已到账)")
    @TableField("status")
    private Integer status;
    @ApiModelProperty(value = "到账时间")
    @TableField("arrival_time")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime arrivalTime;
    @ApiModelProperty(value = "删除(0=否,1=是)")
    @TableField("del_flag")
    private Integer delFlag;
    @ApiModelProperty(value = "添加时间")
    @TableField("create_time")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @TableField(exist = false)
    @ApiModelProperty(value = "联系电话")
    private String phone;
    @TableField(exist = false)
    @ApiModelProperty(value = "用户名称")
    private String name;
    @ApiModelProperty("收款银行卡号")
    @TableField("receiverAccountNoEnc")
    private String receiverAccountNoEnc;
    @ApiModelProperty("收款银行卡持卡人名称")
    @TableField("receiverNameEnc")
    private String receiverNameEnc;
    @ApiModelProperty("账户类型(对私账户201,对公账户204)")
    @TableField("receiverAccountType")
    private Integer receiverAccountType;
    @ApiModelProperty(value = "提现单号")
    @TableField("code")
    private String code;
    @ApiModelProperty(value = "提现流水号")
    @TableField("order_number")
    private String orderNumber;
    @ApiModelProperty("收款方开户行编号")
    @TableField("receiverBankChannelNo")
    private String receiverBankChannelNo;
}
ruoyi-service/ruoyi-account/pom.xml
@@ -16,6 +16,11 @@
    <dependencies>
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-api-order</artifactId>
        </dependency>
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/AppUserBankController.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.account.api.model.AppUserBank;
import com.ruoyi.account.dto.SaveWithdrawalAccount;
import com.ruoyi.account.service.AppUserBankService;
import com.ruoyi.common.core.utils.bean.BeanUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
@@ -33,18 +34,24 @@
    @ResponseBody
    @PostMapping("/saveAppUserBank")
    @ApiOperation(value = "保存银行卡")
    public AjaxResult saveAppUserBank(@RequestBody AppUserBank appUserBank){
    public AjaxResult saveAppUserBank(@RequestBody SaveWithdrawalAccount appUserBank){
        LoginUser loginUserApplet = tokenService.getLoginUserApplet();
        AppUserBank bank = appUserBankService.getOne(new LambdaQueryWrapper<AppUserBank>()
                .eq(AppUserBank::getAppUserId, loginUserApplet.getUserid()));
        if (bank == null){
            appUserBank.setAppUserId(loginUserApplet.getUserid());
            appUserBankService.saveOrUpdate(appUserBank);
            AppUserBank bank1 = new AppUserBank();
            bank1.setAppUserId(loginUserApplet.getUserid());
            bank1.setBankNumber(appUserBank.getReceiverAccountNoEnc());
            bank1.setUserName(appUserBank.getReceiverNameEnc());
            bank1.setReceiverAccountType(appUserBank.getReceiverAccountType());
            appUserBankService.saveOrUpdate(bank1);
        }else {
            appUserBank.setId(bank.getId());
            appUserBank.setAppUserId(bank.getAppUserId());
            appUserBankService.updateById(appUserBank);
            bank.setAppUserId(loginUserApplet.getUserid());
            bank.setBankNumber(appUserBank.getReceiverAccountNoEnc());
            bank.setUserName(appUserBank.getReceiverNameEnc());
            bank.setReceiverAccountType(appUserBank.getReceiverAccountType());
            appUserBankService.updateById(bank);
        }
        return AjaxResult.success();
    }
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/AppUserController.java
@@ -14,11 +14,13 @@
import com.ruoyi.account.mapper.AppUserMapper;
import com.ruoyi.account.service.*;
import com.ruoyi.account.util.ObsUploadUtil;
import com.ruoyi.account.util.UUIDUtil;
import com.ruoyi.account.util.weChat.EnvVersion;
import com.ruoyi.account.util.weChat.WeChatUtil;
import com.ruoyi.account.vo.*;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.poi.ExcelUtil;
import com.ruoyi.common.core.web.controller.BaseController;
@@ -29,6 +31,8 @@
import com.ruoyi.order.feignClient.RemoteOrderGoodsClient;
import com.ruoyi.order.model.Order;
import com.ruoyi.other.api.domain.Shop;
import com.ruoyi.other.api.domain.ShopBalanceStatement;
import com.ruoyi.other.api.domain.ShopWithdraw;
import com.ruoyi.other.api.feignClient.ShopClient;
import com.ruoyi.system.api.domain.SysConfig;
import com.ruoyi.system.api.domain.SysUser;
@@ -53,8 +57,10 @@
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
@@ -81,6 +87,12 @@
    @Resource
    private AppUserService appUserService;
    @Resource
    private IUserWithdrawService userWithdrawService;
    @Resource
    private UserPointService userPointService;
    @Resource
    private AppUserBankService appUserBankService;
    @Resource
    private AppUserMapper appUserMapper;
    @Resource
    private ShopClient shopClient;
@@ -106,6 +118,82 @@
    @Value("${file.upload.location}")
    private String filePath;
    @PostMapping("/verifyBankInfo")
    @ApiOperation(value = "用户提现前校验银行卡信息")
    public R<Boolean> mobileLogin() {
        LoginUser loginUserApplet = tokenService.getLoginUserApplet();
        AppUser appUser = appUserService.getById(loginUserApplet.getUserid());
        AppUserBank bank = appUserBankService.lambdaQuery().eq(AppUserBank::getAppUserId, appUser.getId()).last("limit 1")
                .one();
        if (bank == null){
            return R.ok(false);
        }else{
            return R.ok(true);
        }
    }
    @ApiOperation(value = "提现申请")
    @GetMapping("/withdrawalApplication")
    @ResponseBody
    public R withdrawalApplication(@ApiParam("提现金额") @RequestParam BigDecimal money) {
        LoginUser loginUser = tokenService.getLoginUserApplet();
        SysConfig data = sysConfigClient.getInfo(8L).getData();
        AppUser appUser = appUserService.getById(loginUser.getUserid());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        String code = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
        money=money.setScale(2, BigDecimal.ROUND_HALF_DOWN);
        if (money.compareTo(BigDecimal.ZERO)==0){
            throw new ServiceException("提现金额必须大于零");
        }
        Integer rate = Integer.valueOf(data.getConfigValue());
        BigDecimal availablePoint = new BigDecimal(appUser.getAvailablePoint());
        BigDecimal unMoney = availablePoint.divide(BigDecimal.valueOf(rate), 2, RoundingMode.HALF_DOWN);
        if (unMoney.compareTo( money)<0){
            throw new ServiceException("提现金额不能大于可提现金额,当前可提现金额为:"+unMoney);
        }
        AppUserBank bank = appUserBankService.lambdaQuery().eq(AppUserBank::getAppUserId, appUser.getId()).last("limit 1")
                .one();
        if(bank==null){
            throw new ServiceException("请完善账户信息后再申请提现!");
        }
        if(!org.springframework.util.StringUtils.hasLength(bank.getBankNumber())){
            throw new ServiceException("请完善银行卡后再申请提现!");
        }
        // 增加用户提现积分变动记录
        UserPoint one = userPointService.lambdaQuery().eq(UserPoint::getAppUserId, appUser.getId())
                .orderByDesc(UserPoint::getCreateTime).last("limit 1").one();
        UserWithdraw userWithdraw = new UserWithdraw();
        userWithdraw.setAppUserId(appUser.getId());
        userWithdraw.setMoney(money);
        BigDecimal multiply = money.multiply(BigDecimal.valueOf(rate));
        appUser.setAvailablePoint(appUser.getAvailablePoint()-multiply.setScale(0, RoundingMode.HALF_UP).intValue());
        if (multiply.compareTo(new BigDecimal(appUser.getAvailablePoint()))>0){
            throw new ServiceException("积分不足!");
        }
        userWithdraw.setIntegral(Integer.valueOf(multiply.setScale(0, RoundingMode.HALF_UP).toString()));
        userWithdraw.setAuditStatus(0);
        userWithdraw.setStatus(1);
        userWithdraw.setCode(code);
        userWithdraw.setCreateTime(LocalDateTime.now());
        if (one.getBalance() - Integer.valueOf(multiply.setScale(0, RoundingMode.HALF_UP).toString())<0){
            throw new ServiceException("积分不足!");
        }
        userWithdraw.setReceiverAccountNoEnc(bank.getBankNumber());
        userWithdraw.setReceiverNameEnc(bank.getUserName());
        userWithdraw.setReceiverBankChannelNo(bank.getReceiverBankChannelNo());
        userWithdraw.setReceiverAccountType(201);
        userWithdrawService.save(userWithdraw);
        UserPoint userPoint = new UserPoint();
        userPoint.setType(18);
        userPoint.setVariablePoint(Integer.valueOf(multiply.setScale(0, RoundingMode.HALF_UP).toString()));
        userPoint.setHistoricalPoint(one.getBalance());
        userPoint.setBalance(one.getBalance() - Integer.valueOf(multiply.setScale(0, RoundingMode.HALF_UP).toString()));
        userPoint.setCreateTime(LocalDateTime.now());
        userPoint.setAppUserId(appUser.getId());
        userPoint.setObjectId(userWithdraw.getId());
        userPointService.save(userPoint);
        appUserService.updateById(appUser);
        return R.ok();
    }
    @ResponseBody
    @PostMapping("/mobileLogin")
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/UserWithdrawController.java
New file
@@ -0,0 +1,169 @@
package com.ruoyi.account.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.api.model.AppUser;
import com.ruoyi.account.api.model.UserPoint;
import com.ruoyi.account.api.model.UserWithdraw;
import com.ruoyi.account.mapper.UserPointMapper;
import com.ruoyi.account.mapper.UserWithdrawMapper;
import com.ruoyi.account.service.AppUserService;
import com.ruoyi.account.service.IUserWithdrawService;
import com.ruoyi.account.service.UserPointService;
import com.ruoyi.account.util.BankCode;
import com.ruoyi.account.util.HttpRequester;
import com.ruoyi.account.util.HttpRespons;
import com.ruoyi.account.util.withdraw.HttpUtilWithdraw;
import com.ruoyi.account.vo.*;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.poi.ExcelUtil;
import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.page.PageInfo;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.order.feignClient.OrderClient;
import com.ruoyi.order.model.Order;
import com.ruoyi.other.api.domain.Shop;
import com.ruoyi.other.api.domain.ShopBalanceStatement;
import com.ruoyi.other.api.domain.ShopWithdraw;
import com.ruoyi.system.api.model.LoginUser;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author luodangjia
 * @since 2024-11-21
 */
@RestController
@RequestMapping("/user-point")
@Api(tags = "用户提现列表")
public class UserWithdrawController extends BaseController {
    @Resource
    private IUserWithdrawService userWithdrawService;
    @Resource
    private UserPointService userPointService;
    @Resource
    private UserWithdrawMapper userWithdrawMapper;
    @Resource
    private AppUserService appUserService;
    @Resource
    private TokenService tokenService;
    @GetMapping("/shop/list")
    @ApiOperation(value = "用户提现申请列表")
    public R<IPage<UserWithdraw>> shoplist(@ApiParam("页码") @RequestParam Integer pageNum,
                                           @ApiParam("每一页数据大小") Integer pageSize,
                                           @ApiParam("用户手机号") String phone,
                                           @ApiParam("审核状态(0=待审核,1=审核通过,2=审核失败)")Integer auditStatus) {
        //模糊查询手机号
        List<Long> appUserIds=new ArrayList<>();
        if (StringUtils.isNotEmpty(phone)) {
            QueryWrapper<AppUser> queryWrapper=new QueryWrapper<>();
            queryWrapper.like(StringUtils.isNotEmpty(phone),"phone", phone);
            appUserIds=appUserService.list(queryWrapper).stream().map(AppUser::getId).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(appUserIds)) {
                return R.ok(new PageInfo<>());
            }
        }
        PageInfo<UserWithdraw> page=new PageInfo<>(pageNum,pageSize);
        List<UserWithdraw> list =userWithdrawMapper.page(page,appUserIds,auditStatus);
        for (UserWithdraw userWithdraw : list) {
            userWithdraw.setUid(userWithdraw.getId().toString());
        }
        page.setRecords(list);
        return R.ok(page);
    }
    /**
     * 审核
     */
    @GetMapping("/audit")
    @ApiOperation("提现审核")
    public R<Void> audit(@ApiParam("审核状态 1=通过 2=失败") @RequestParam Integer auditstatus,
                         @ApiParam("提现id") Long id,
                         @ApiParam("审核失败备注") String auditMsg
    ) throws IOException {
        LoginUser loginUser = tokenService.getLoginUser();
        UserWithdraw userWithdraw = userWithdrawService.getById(id);
        if(0 != userWithdraw.getAuditStatus()){
            return R.fail("不能重复审核");
        }
        AppUser appUser = appUserService.getById(userWithdraw.getAppUserId());
        if(1 == auditstatus){
            // 打款
            String url = "https://ccdcapi.alipay.com/validateAndCacheCardInfo.json?_input_charset=utf-8&" +
                    "cardNo="+userWithdraw.getReceiverAccountNoEnc()+"&cardBinCheck=true";
            HashMap<String, String> hashMap = new HashMap<>();
            HttpRequester hr = new HttpRequester();
            HttpRespons HP = hr.sendPost(url, hashMap);
            System.out.println("接收返回参数:" + HP.getContent());
            com.alibaba.fastjson.JSONObject resPay = com.alibaba.fastjson.JSONObject.parseObject(HP.getContent());
            System.err.println(resPay);
            if (resPay.getString("validated")==null){
                System.err.println("不合法的银行卡号");
            }
            String bankCode = resPay.getString("bank");
            String bankName = BankCode.getBankNameByCode(bankCode); // 返回ABC枚举实例
            System.err.println(bankName);
            String withdraw = HttpUtilWithdraw.userWithdraw(userWithdraw, bankName);
            if (!withdraw.equals("success")){
                return R.fail("打款失败,原因:"+withdraw);
            }
            userWithdraw.setStatus(2);
            userWithdraw.setArrivalTime(LocalDateTime.now());
            userWithdrawService.updateById(userWithdraw);
        }
        if(2 == auditstatus){
            //审核不通过
            //回退积分和添加变动明细
            UserPoint userPoint = new UserPoint();
            userPoint.setType(19);
            UserPoint one = userPointService.lambdaQuery().eq(UserPoint::getAppUserId, appUser.getId())
                    .orderByDesc(UserPoint::getCreateTime).last("limit 1").one();
            userPoint.setVariablePoint(userWithdraw.getIntegral());
            userPoint.setHistoricalPoint(one.getBalance());
            userPoint.setBalance(one.getBalance() + userWithdraw.getIntegral());
            userPoint.setCreateTime(LocalDateTime.now());
            userPoint.setAppUserId(appUser.getId());
            userPoint.setObjectId(userWithdraw.getId());
            userPointService.save(userPoint);
            // 回退积分
            appUser.setAvailablePoint(appUser.getAvailablePoint()+userWithdraw.getIntegral());
            appUserService.updateById(appUser);
        }
        userWithdraw.setAuditStatus(auditstatus);
        userWithdraw.setAuditUserId(loginUser.getUserid());
        userWithdraw.setAuditTime(LocalDateTime.now());
        userWithdraw.setAuditMsg(auditMsg);
        userWithdrawService.updateById(userWithdraw);
        return R.ok();
    }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/dto/SaveWithdrawalAccount.java
New file
@@ -0,0 +1,25 @@
package com.ruoyi.account.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * @author zhibing.pu
 * @Date 2025/1/1 11:54
 */
@Data
public class SaveWithdrawalAccount {
    @ApiModelProperty("收款银行卡号")
    private String receiverAccountNoEnc;
    @ApiModelProperty("收款银行卡持卡人名称")
    private String receiverNameEnc;
    @ApiModelProperty("账户类型(对私账户201,对公账户204)")
    private Integer receiverAccountType;
    @ApiModelProperty("收款账户联行号")
    private String receiverBankChannelNo;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/mapper/UserWithdrawMapper.java
New file
@@ -0,0 +1,24 @@
package com.ruoyi.account.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.account.api.model.UserWithdraw;
import com.ruoyi.common.core.web.page.PageInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
 * 用户提现记录 Mapper 接口
 * </p>
 *
 * @author lingma
 * @since 2025-04-05
 */
@Mapper
public interface UserWithdrawMapper extends BaseMapper<UserWithdraw> {
    List<UserWithdraw> page(PageInfo<UserWithdraw> page, @Param("appUserIds")List<Long> appUserIds, @Param("auditStatus")Integer auditStatus);
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/IUserWithdrawService.java
New file
@@ -0,0 +1,17 @@
package com.ruoyi.account.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.account.api.model.UserWithdraw;
/**
 * <p>
 * 用户提现记录 服务类
 * </p>
 *
 * @author lingma
 * @since 2025-04-05
 */
public interface IUserWithdrawService extends IService<UserWithdraw> {
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/UserWithdrawServiceImpl.java
New file
@@ -0,0 +1,20 @@
package com.ruoyi.account.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.api.model.UserWithdraw;
import com.ruoyi.account.mapper.UserWithdrawMapper;
import com.ruoyi.account.service.IUserWithdrawService;
import org.springframework.stereotype.Service;
/**
 * <p>
 * 用户提现记录 服务实现类
 * </p>
 *
 * @author lingma
 * @since 2025-04-05
 */
@Service
public class UserWithdrawServiceImpl extends ServiceImpl<UserWithdrawMapper, UserWithdraw> implements IUserWithdrawService {
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/BankCode.java
New file
@@ -0,0 +1,217 @@
package com.ruoyi.account.util;
/**
 * 银行代码枚举类
 * 用于通过银行简称获取对应的银行名称
 */
public enum BankCode {
    SRCB("SRCB", "深圳农村商业银行"),
    BGB("BGB", "广西北部湾银行"),
    SHRCB("SHRCB", "上海农村商业银行"),
    BJBANK("BJBANK", "北京银行"),
    WHCCB("WHCCB", "威海市商业银行"),
    BOZK("BOZK", "周口银行"),
    KORLABANK("KORLABANK", "库尔勒市商业银行"),
    SPABANK("SPABANK", "平安银行"),
    SDEB("SDEB", "顺德农商银行"),
    HURCB("HURCB", "湖北省农村信用社"),
    WRCB("WRCB", "无锡农村商业银行"),
    BOCY("BOCY", "朝阳银行"),
    CZBANK("CZBANK", "浙商银行"),
    HDBANK("HDBANK", "邯郸银行"),
    BOC("BOC", "中国银行"),
    BOD("BOD", "东莞银行"),
    CCB("CCB", "中国建设银行"),
    ZYCBANK("ZYCBANK", "遵义市商业银行"),
    SXCB("SXCB", "绍兴银行"),
    GZRCU("GZRCU", "贵州省农村信用社"),
    ZJKCCB("ZJKCCB", "张家口市商业银行"),
    BOJZ("BOJZ", "锦州银行"),
    BOP("BOP", "平顶山银行"),
    HKB("HKB", "汉口银行"),
    SPDB("SPDB", "上海浦东发展银行"),
    NXRCU("NXRCU", "宁夏黄河农村商业银行"),
    NYNB("NYNB", "广东南粤银行"),
    GRCB("GRCB", "广州农商银行"),
    BOSZ("BOSZ", "苏州银行"),
    HZCB("HZCB", "杭州银行"),
    HSBK("HSBK", "衡水银行"),
    HBC("HBC", "湖北银行"),
    JXBANK("JXBANK", "嘉兴银行"),
    HRXJB("HRXJB", "华融湘江银行"),
    BODD("BODD", "丹东银行"),
    AYCB("AYCB", "安阳银行"),
    EGBANK("EGBANK", "恒丰银行"),
    CDB("CDB", "国家开发银行"),
    TCRCB("TCRCB", "江苏太仓农村商业银行"),
    NJCB("NJCB", "南京银行"),
    ZZBANK("ZZBANK", "郑州银行"),
    DYCB("DYCB", "德阳商业银行"),
    YBCCB("YBCCB", "宜宾市商业银行"),
    SCRCU("SCRCU", "四川省农村信用"),
    KLB("KLB", "昆仑银行"),
    LSBANK("LSBANK", "莱商银行"),
    YDRCB("YDRCB", "尧都农商行"),
    CCQTGB("CCQTGB", "重庆三峡银行"),
    FDB("FDB", "富滇银行"),
    JSRCU("JSRCU", "江苏省农村信用联合社"),
    JNBANK("JNBANK", "济宁银行"),
    CMB("CMB", "招商银行"),
    JINCHB("JINCHB", "晋城银行JCBANK"),
    FXCB("FXCB", "阜新银行"),
    WHRCB("WHRCB", "武汉农村商业银行"),
    HBYCBANK("HBYCBANK", "湖北银行宜昌分行"),
    TZCB("TZCB", "台州银行"),
    TACCB("TACCB", "泰安市商业银行"),
    XCYH("XCYH", "许昌银行"),
    CEB("CEB", "中国光大银行"),
    NXBANK("NXBANK", "宁夏银行"),
    HSBANK("HSBANK", "徽商银行"),
    JJBANK("JJBANK", "九江银行"),
    NHQS("NHQS", "农信银清算中心"),
    MTBANK("MTBANK", "浙江民泰商业银行"),
    LANGFB("LANGFB", "廊坊银行"),
    ASCB("ASCB", "鞍山银行"),
    KSRB("KSRB", "昆山农村商业银行"),
    YXCCB("YXCCB", "玉溪市商业银行"),
    DLB("DLB", "大连银行"),
    DRCBCL("DRCBCL", "东莞农村商业银行"),
    GCB("GCB", "广州银行"),
    NBBANK("NBBANK", "宁波银行"),
    BOYK("BOYK", "营口银行"),
    SXRCCU("SXRCCU", "陕西信合"),
    GLBANK("GLBANK", "桂林银行"),
    BOQH("BOQH", "青海银行"),
    CDRCB("CDRCB", "成都农商银行"),
    QDCCB("QDCCB", "青岛银行"),
    HKBEA("HKBEA", "东亚银行"),
    HBHSBANK("HBHSBANK", "湖北银行黄石分行"),
    WZCB("WZCB", "温州银行"),
    TRCB("TRCB", "天津农商银行"),
    QLBANK("QLBANK", "齐鲁银行"),
    GDRCC("GDRCC", "广东省农村信用社联合社"),
    ZJTLCB("ZJTLCB", "浙江泰隆商业银行"),
    GZB("GZB", "赣州银行"),
    GYCB("GYCB", "贵阳市商业银行"),
    CQBANK("CQBANK", "重庆银行"),
    DAQINGB("DAQINGB", "龙江银行"),
    CGNB("CGNB", "南充市商业银行"),
    SCCB("SCCB", "三门峡银行"),
    CSRCB("CSRCB", "常熟农村商业银行"),
    SHBANK("SHBANK", "上海银行"),
    JLBANK("JLBANK", "吉林银行"),
    CZRCB("CZRCB", "常州农村信用联社"),
    BANKWF("BANKWF", "潍坊银行"),
    ZRCBANK("ZRCBANK", "张家港农村商业银行"),
    FJHXBC("FJHXBC", "福建海峡银行"),
    ZJNX("ZJNX", "浙江省农村信用社联合社"),
    LZYH("LZYH", "兰州银行"),
    JSB("JSB", "晋商银行"),
    BOHAIB("BOHAIB", "渤海银行"),
    CZCB("CZCB", "浙江稠州商业银行"),
    YQCCB("YQCCB", "阳泉银行"),
    SJBANK("SJBANK", "盛京银行"),
    XABANK("XABANK", "西安银行"),
    BSB("BSB", "包商银行"),
    JSBANK("JSBANK", "江苏银行"),
    FSCB("FSCB", "抚顺银行"),
    HNRCU("HNRCU", "河南省农村信用"),
    COMM("COMM", "交通银行"),
    XTB("XTB", "邢台银行"),
    CITIC("CITIC", "中信银行"),
    HXBANK("HXBANK", "华夏银行"),
    HNRCC("HNRCC", "湖南省农村信用社"),
    DYCCB("DYCCB", "东营市商业银行"),
    ORBANK("ORBANK", "鄂尔多斯银行"),
    BJRCB("BJRCB", "北京农村商业银行"),
    XYBANK("XYBANK", "信阳银行"),
    ZGCCB("ZGCCB", "自贡市商业银行"),
    CDCB("CDCB", "成都银行"),
    HANABANK("HANABANK", "韩亚银行"),
    CMBC("CMBC", "中国民生银行"),
    LYBANK("LYBANK", "洛阳银行"),
    GDB("GDB", "广东发展银行"),
    ZBCB("ZBCB", "齐商银行"),
    CBKF("CBKF", "开封市商业银行"),
    H3CB("H3CB", "内蒙古银行"),
    CIB("CIB", "兴业银行"),
    CRCBANK("CRCBANK", "重庆农村商业银行"),
    SZSBK("SZSBK", "石嘴山银行"),
    DZBANK("DZBANK", "德州银行"),
    SRBANK("SRBANK", "上饶银行"),
    LSCCB("LSCCB", "乐山市商业银行"),
    JXRCU("JXRCU", "江西省农村信用"),
    ICBC("ICBC", "中国工商银行"),
    JZBANK("JZBANK", "晋中市商业银行"),
    HZCCB("HZCCB", "湖州市商业银行"),
    NHB("NHB", "南海农村信用联社"),
    XXBANK("XXBANK", "新乡银行"),
    JRCB("JRCB", "江苏江阴农村商业银行"),
    YNRCC("YNRCC", "云南省农村信用社"),
    ABC("ABC", "中国农业银行"),
    GXRCU("GXRCU", "广西省农村信用"),
    PSBC("PSBC", "中国邮政储蓄银行"),
    BZMD("BZMD", "驻马店银行"),
    ARCU("ARCU", "安徽省农村信用社"),
    GSRCU("GSRCU", "甘肃省农村信用"),
    LYCB("LYCB", "辽阳市商业银行"),
    JLRCU("JLRCU", "吉林农信"),
    URMQCCB("URMQCCB", "乌鲁木齐市商业银行"),
    XLBANK("XLBANK", "中山小榄村镇银行"),
    CSCB("CSCB", "长沙银行"),
    JHBANK("JHBANK", "金华银行"),
    BHB("BHB", "河北银行"),
    NBYZ("NBYZ", "鄞州银行"),
    LSBC("LSBC", "临商银行"),
    BOCD("BOCD", "承德银行"),
    SDRCU("SDRCU", "山东农信"),
    NCB("NCB", "南昌银行"),
    TCCB("TCCB", "天津银行"),
    WJRCB("WJRCB", "吴江农商银行"),
    CBBQS("CBBQS", "城市商业银行资金清算中心"),
    HBRCU("HBRCU", "河北省农村信用社");
    private final String code;
    private final String name;
    BankCode(String code, String name) {
        this.code = code;
        this.name = name;
    }
    public String getCode() {
        return code;
    }
    public String getName() {
        return name;
    }
    /**
     * 根据银行简称获取银行名称
     * @param code 银行简称
     * @return 银行名称,如果未找到返回null
     */
    public static String getBankNameByCode(String code) {
        for (BankCode bankCode : values()) {
            if (bankCode.getCode().equals(code)) {
                return bankCode.getName();
            }
        }
        return null;
    }
    /**
     * 根据银行简称获取枚举实例
     * @param code 银行简称
     * @return BankCode枚举实例,如果未找到返回null
     */
    public static BankCode getBankByCode(String code) {
        for (BankCode bankCode : values()) {
            if (bankCode.getCode().equals(code)) {
                return bankCode;
            }
        }
        return null;
    }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/HttpRequester.java
New file
@@ -0,0 +1,204 @@
package com.ruoyi.account.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Vector;
public class HttpRequester {
    private String defaultContentEncoding;
    public HttpRequester() {
        this.defaultContentEncoding = Charset.defaultCharset().name();
    }
    /**
     * 发送POST请求
     *
     * @param urlString
     *            URL地址
     * @return 响应对象
     * @throws IOException
     */
    public HttpRespons sendPost(String urlString) throws IOException {
        return this.send(urlString, "POST", null, null);
    }
    /**
     * 发送POST请求
     *
     * @param urlString
     *            URL地址
     * @param params
     *            参数集合
     * @return 响应对象
     * @throws IOException
     */
    public HttpRespons sendPost(String urlString, Map<String, String> params)
            throws IOException {
        for(String key : params.keySet()) {
            if(params.get(key)!=null&&!"".equals(params.get(key))) {
                params.put(key, (String)URLEncoder.encode(params.get(key),"utf-8")) ;//(String) 强制类型转换
            }
        }
        return this.send(urlString, "POST", params, null);
    }
    /**
     * 发送POST请求
     *
     * @param urlString
     *            URL地址
     * @param params
     *            参数集合
     * @param propertys
     *            请求属性
     * @return 响应对象
     * @throws IOException
     */
    public HttpRespons sendPost(String urlString, Map<String, String> params,
            Map<String, String> propertys) throws IOException {
        return this.send(urlString, "POST", params, propertys);
    }
    /**
     * 发送HTTP请求
     *
     * @param urlString
     * @return 响映对象
     * @throws IOException
     */
    private HttpRespons send(String urlString, String method,
            Map<String, String> parameters, Map<String, String> propertys)
            throws IOException {
        HttpURLConnection urlConnection = null;
        if (method.equalsIgnoreCase("GET") && parameters != null) {
            StringBuffer param = new StringBuffer();
            int i = 0;
            for (String key : parameters.keySet()) {
                if (i == 0)
                    param.append("?");
                else
                    param.append("&");
                param.append(key).append("=").append(parameters.get(key));
                i++;
            }
            urlString += param;
        }
        URL url = new URL(urlString);
        urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setRequestMethod(method);
        urlConnection.setDoOutput(true);
        urlConnection.setDoInput(true);
        urlConnection.setUseCaches(false);
        if (propertys != null)
            for (String key : propertys.keySet()) {
                urlConnection.addRequestProperty(key, propertys.get(key));
            }
        if (method.equalsIgnoreCase("POST") && parameters != null) {
            StringBuffer param = new StringBuffer();
            for (String key : parameters.keySet()) {
                param.append("&");
                param.append(key).append("=").append(parameters.get(key));
            }
            urlConnection.getOutputStream().write(param.toString().getBytes());
            urlConnection.getOutputStream().flush();
            urlConnection.getOutputStream().close();
        }
        return this.makeContent(urlString, urlConnection);
    }
    /**
     * 得到响应对象
     *
     * @param urlConnection
     * @return 响应对象
     * @throws IOException
     */
    private HttpRespons makeContent(String urlString,
            HttpURLConnection urlConnection) throws IOException {
        HttpRespons httpResponser = new HttpRespons();
        try {
            InputStream in = urlConnection.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(in));
            httpResponser.contentCollection = new Vector<String>();
            StringBuffer temp = new StringBuffer();
            String line = bufferedReader.readLine();
            while (line != null) {
                httpResponser.contentCollection.add(line);
                temp.append(line).append("\r\n");
                line = bufferedReader.readLine();
            }
            bufferedReader.close();
            String ecod = urlConnection.getContentEncoding();
            if (ecod == null)
                ecod = this.defaultContentEncoding;
            httpResponser.urlString = urlString;
            httpResponser.defaultPort = urlConnection.getURL().getDefaultPort();
            httpResponser.file = urlConnection.getURL().getFile();
            httpResponser.host = urlConnection.getURL().getHost();
            httpResponser.path = urlConnection.getURL().getPath();
            httpResponser.port = urlConnection.getURL().getPort();
            httpResponser.protocol = urlConnection.getURL().getProtocol();
            httpResponser.query = urlConnection.getURL().getQuery();
            httpResponser.ref = urlConnection.getURL().getRef();
            httpResponser.userInfo = urlConnection.getURL().getUserInfo();
            httpResponser.content = new String(temp.toString().getBytes(), ecod);
            httpResponser.contentEncoding = ecod;
            httpResponser.code = urlConnection.getResponseCode();
            httpResponser.message = urlConnection.getResponseMessage();
            httpResponser.contentType = urlConnection.getContentType();
            httpResponser.method = urlConnection.getRequestMethod();
            httpResponser.connectTimeout = urlConnection.getConnectTimeout();
            httpResponser.readTimeout = urlConnection.getReadTimeout();
            return httpResponser;
        } catch (IOException e) {
            throw e;
        } finally {
            if (urlConnection != null)
                urlConnection.disconnect();
        }
    }
    /**
     * 默认的响应字符集
     */
    public String getDefaultContentEncoding() {
        return this.defaultContentEncoding;
    }
    /**
     * 设置默认的响应字符集
     */
    public void setDefaultContentEncoding(String defaultContentEncoding) {
        this.defaultContentEncoding = defaultContentEncoding;
    }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/HttpRespons.java
New file
@@ -0,0 +1,184 @@
package com.ruoyi.account.util;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;
public class HttpRespons {
      String urlString;
        int defaultPort;
        String file;
        String host;
        String path;
        int port;
        String protocol;
        String query;
        String ref;
        String userInfo;
        String contentEncoding;
        String content;
        String contentType;
        int code;
        String message;
        String method;
        int connectTimeout;
        int readTimeout;
        Vector<String> contentCollection;
        public String getContent() {
            return content;
        }
        public String getContentType() {
            return contentType;
        }
        public int getCode() {
            return code;
        }
        public String getMessage() {
            return message;
        }
        public Vector<String> getContentCollection() {
            return contentCollection;
        }
        public String getContentEncoding() {
            return contentEncoding;
        }
        public String getMethod() {
            return method;
        }
        public int getConnectTimeout() {
            return connectTimeout;
        }
        public int getReadTimeout() {
            return readTimeout;
        }
        public String getUrlString() {
            return urlString;
        }
        public int getDefaultPort() {
            return defaultPort;
        }
        public String getFile() {
            return file;
        }
        public String getHost() {
            return host;
        }
        public String getPath() {
            return path;
        }
        public int getPort() {
            return port;
        }
        public String getProtocol() {
            return protocol;
        }
        public String getQuery() {
            return query;
        }
        public String getRef() {
            return ref;
        }
        public String getUserInfo() {
            return userInfo;
        }
        public static String post(String params,String requestUrl) throws IOException {
//            try {
               //HttpRequester request = new HttpRequester();
               // request.setDefaultContentEncoding("utf-8");
                byte[] requestBytes = params.getBytes("utf-8"); // 将参数转为二进制流
                       HttpClient httpClient = new HttpClient(); // 客户端实例化
                       PostMethod postMethod = new PostMethod(requestUrl);
                       //设置请求头Authorization
//                       postMethod.setRequestHeader("Authorization", "Basic " + authorization);
                       // 设置请求头  Content-Type
                       postMethod.setRequestHeader("Content-Type", "application/json");
                       InputStream inputStream = new ByteArrayInputStream(requestBytes, 0,requestBytes.length);
                       RequestEntity requestEntity = new InputStreamRequestEntity(inputStream,
                                 requestBytes.length, "application/json; charset=utf-8"); // 请求体
                       postMethod.setRequestEntity(requestEntity);
                       httpClient.executeMethod(postMethod);// 执行请求
                       InputStream soapResponseStream = postMethod.getResponseBodyAsStream();// 获取返回的流
                       byte[] datas = null;
                        try {
                            datas = readInputStream(soapResponseStream);// 从输入流中读取数据
                        } catch (Exception e) {
                            e.printStackTrace();
                         }
                        String result = new String(datas, "UTF-8");// 将二进制流转为String
                         // 打印返回结果
                         // System.out.println(result);
                         return result;
        }
        /**
              * 从输入流中读取数据
              *
              * @param inStream
              * @return
              * @throws Exception
        */
        public static byte[] readInputStream(InputStream inStream) throws Exception {
                     ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                     byte[] buffer = new byte[1024];
                     int len = 0;
                     while ((len = inStream.read(buffer)) != -1) {
                        outStream.write(buffer, 0, len);
                     }
                     byte[] data = outStream.toByteArray();
                     outStream.close();
                     inStream.close();
                    return data;
                 }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/UUIDUtil.java
New file
@@ -0,0 +1,103 @@
package com.ruoyi.account.util;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
/**
 * 定义生成随机码的工具类
 */
public class UUIDUtil {
    private int i = 1;
    /**
     * 定义生成原生的UUID随机码
     *
     * @return
     */
    public static String getNativeUUID() {
        return UUID.randomUUID().toString();
    }
    /**
     * 生成32位随机码
     *
     * @return
     */
    public static String getRandomCode() {
        return UUIDUtil.getNativeUUID().replaceAll("-", "");
    }
    /**
     * 获取给定长度的随机码
     *
     * @param num
     * @return
     * @throws Exception
     */
    public static String getRandomCode(Integer num) throws Exception {
        String str = null;
        if (0 < num) {
            if (num % 32 > 0) {
                Integer s = num / 32;
                Integer l = num % 32;
                StringBuffer sb = new StringBuffer();
                for (int i = 0; i < s; i++) {
                    sb.append(UUIDUtil.getRandomCode());
                }
                sb.append(UUIDUtil.getRandomCode().substring(0, l));
                str = sb.toString();
            } else if (num % 32 == 0) {
                Integer s = num / 32;
                StringBuffer sb = new StringBuffer();
                for (int i = 0; i < s; i++) {
                    sb.append(UUIDUtil.getRandomCode());
                }
                str = sb.toString();
            } else {
                str = UUIDUtil.getRandomCode().substring(0, num);
            }
        } else {
            throw new Exception("参数只能大于0");
        }
        return str;
    }
    /**
     * 获取根据当前时间的字符串数据
     *
     * @return
     */
    public synchronized static String getTimeStr() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddhhmmssS");
        return simpleDateFormat.format(new Date());
    }
    /**
     * @Description: 获取数字随机码
     * @Author pzb
     * @Date 2021/8/11 16:52
     * @Param
     * @Return
     * @Exception
     */
    public static String getNumberRandom(Integer num) {
        if (null == num) {
            num = 32;
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < num; i++) {
            sb.append(Double.valueOf(Math.random() * 10).intValue());
        }
        return sb.toString();
    }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/withdraw/HttpUtilWithdraw.java
New file
@@ -0,0 +1,492 @@
package com.ruoyi.account.util.withdraw;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.ruoyi.account.api.model.UserWithdraw;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.other.api.domain.ShopWithdraw;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * 三方提现工具类
 */
public class HttpUtilWithdraw {
    //设置请求和传输超时时间
    private static RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
    //初始化需要用到的参数
    private static final String CLIENT_TYPE = "1";
    private static final String CLIENT_VERSION = "1.0.0";
    private static final String APP_CONFIG_ID = "8597918391594056444";
    private static final String API_KEY = "jo_pay_S4UIWYjWmy46d9yX9N2QWZffD";
    private static final String API_SECRET = "cad03aaf2019530090d642aa9b2f0cbb";
    private static final String STRING_KEY = "pay_open_api_vwHXa2ay1Dv7E${clientType}${clientVersion}${apiKey}${apiSecret}${timestamp}";
    private static final String URL = "https://pay.new.jieoukeji.cn/jo-pay-open-api";
    private static final String PATH = "/v1/safe/withdraw";//示例接口:获取银行卡信息
    private static final Pattern VALUE_PATTERN = Pattern.compile("\\$\\{([^}]*)\\}");
    public static void main(String[] args) throws IOException {
        long timestamp = System.currentTimeMillis();
        Map<String,String> headers = new HashMap<>(6);
        headers.put("client-type",CLIENT_TYPE);
        headers.put("client-version",CLIENT_VERSION);
        headers.put("api-secret",API_SECRET);
        headers.put("api-key",API_KEY);
        headers.put("access-timestamp",String.valueOf(timestamp));
        headers.put("Content-Type","application/json");
        //拼接字符串,签名开头保留,${} 全替换为header参数
        headers.put("access-sign",getSign(STRING_KEY,timestamp));
        //开始请求API接口
        Map<String, Object> param = new HashMap<>();
        param.put("orderCode", "09127389681694");
        param.put("cardNo", "6228480469852935177");
        param.put("name", "周帅");
        param.put("withdrawType", "1");
        param.put("accountType", "00");
        param.put("bankName", "中国农业银行");
//        param.put("brachBankCode", "123546345674573457");
        param.put("money", "0.1");
        param.put("appConfigId", APP_CONFIG_ID);
        try {
            String results = post(URL+PATH,headers, param);
            //返回结果JSON字符串
            System.out.println(results);
        } catch (Exception e) {
            e.printStackTrace();
        }
//        String url = "https://ccdcapi.alipay.com/validateAndCacheCardInfo.json?_input_charset=utf-8&" +
//                "cardNo=" + "6228480469852935177" + "&cardBinCheck=true";
//        HashMap<String, String> hashMap = new HashMap<>();
//        HttpRequester hr = new HttpRequester();
//        HttpRespons HP = hr.sendPost(url, hashMap);
//        System.out.println("接收返回参数:" + HP.getContent());
//        JSONObject resPay = JSONObject.parseObject(HP.getContent());
//        System.err.println(resPay);
//        if (resPay.getString("validated") == null) {
//            System.err.println("不合法的银行卡号");
//        }
//        String bankName = BankCode.getBankNameByCode("ABC"); // 返回ABC枚举实例
//        System.err.println(bankName);
    }
    public static String withdraw(ShopWithdraw shopWithdraw, String bankName) {
        long timestamp = System.currentTimeMillis();
        Map<String, String> headers = new HashMap<>(6);
        headers.put("client-type", CLIENT_TYPE);
        headers.put("client-version", CLIENT_VERSION);
        headers.put("api-secret", API_SECRET);
        headers.put("api-key", API_KEY);
        headers.put("access-timestamp", String.valueOf(timestamp));
        headers.put("Content-Type", "application/json");
        //拼接字符串,签名开头保留,${} 全替换为header参数
        headers.put("access-sign", getSign(STRING_KEY, timestamp));
        //开始请求API接口
        Map<String, Object> param = new HashMap<>();
        param.put("orderCode", shopWithdraw.getCode());
        param.put("cardNo", shopWithdraw.getReceiverAccountNoEnc());
        param.put("name", shopWithdraw.getReceiverNameEnc());
        param.put("withdrawType", "1");
        if (shopWithdraw.getReceiverAccountType().equals(204)) {
            param.put("accountType", "01");
            param.put("bankName", bankName);
            param.put("brachBankCode", shopWithdraw.getReceiverBankChannelNo());
        } else {
            param.put("accountType", "00");
        }
        param.put("money", shopWithdraw.getMoney());
        param.put("appConfigId", APP_CONFIG_ID);
        try {
            String results = post(URL + PATH, headers, param);
            //返回结果JSON字符串
            System.out.println(results);
            JSONObject jsonObject = JSONObject.parseObject(results);
            if (jsonObject.getString("result").equals("true")){
                return "success";
            }else{
                return jsonObject.getString("message");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "打款失败";
    }
    public static String userWithdraw(UserWithdraw userWithdraw, String bankName) {
        long timestamp = System.currentTimeMillis();
        Map<String, String> headers = new HashMap<>(6);
        headers.put("client-type", CLIENT_TYPE);
        headers.put("client-version", CLIENT_VERSION);
        headers.put("api-secret", API_SECRET);
        headers.put("api-key", API_KEY);
        headers.put("access-timestamp", String.valueOf(timestamp));
        headers.put("Content-Type", "application/json");
        //拼接字符串,签名开头保留,${} 全替换为header参数
        headers.put("access-sign", getSign(STRING_KEY, timestamp));
        //开始请求API接口
        Map<String, Object> param = new HashMap<>();
        param.put("orderCode", userWithdraw.getCode());
        param.put("cardNo", userWithdraw.getReceiverAccountNoEnc());
        param.put("name", userWithdraw.getReceiverNameEnc());
        param.put("withdrawType", "1");
        if (userWithdraw.getReceiverAccountType().equals(204)) {
            param.put("accountType", "01");
            param.put("bankName", bankName);
            param.put("brachBankCode", userWithdraw.getReceiverBankChannelNo());
        } else {
            param.put("accountType", "00");
        }
        param.put("money", userWithdraw.getMoney());
        param.put("appConfigId", APP_CONFIG_ID);
        try {
            String results = post(URL + PATH, headers, param);
            //返回结果JSON字符串
            System.out.println(results);
            JSONObject jsonObject = JSONObject.parseObject(results);
            if (jsonObject.getString("result").equals("true")){
                return "success";
            }else{
                return jsonObject.getString("message");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "打款失败";
    }
    /**
     * 发起post请求,请求参数以Map集合形式传入
     *
     * @param httpUrl
     * @param body
     * @return
     * @throws Exception
     */
    public static String post(String httpUrl, Map<String, String> headers, Map<String, Object> body) {
        CloseableHttpClient httpclient = createSSLClientDefault();
        BufferedReader in;
        HttpPost httpPost = new HttpPost(httpUrl);
        try {
            httpPost.setHeader("Accept", "application/json");
            for (Map.Entry<String, String> headerMap : headers.entrySet()) {
                httpPost.addHeader(headerMap.getKey(), headerMap.getValue());
            }
            httpPost.setConfig(requestConfig);
            StringEntity entity = new StringEntity(JSON.toJSONString(body), StandardCharsets.UTF_8);
            entity.setContentType("application/json");
            httpPost.setEntity(entity);
            CloseableHttpResponse response = httpclient.execute(httpPost);
            InputStream content = response.getEntity().getContent();
            in = new BufferedReader(new InputStreamReader(content, StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    /**
     * 发起get请求,请求参数以Map集合形式传入
     *
     * @param httpUrl
     * @param params
     * @return
     * @throws Exception
     */
    public static String get(String httpUrl, Map<String, String> headers, Map<String, String> params) {
        CloseableHttpClient httpclient = createSSLClientDefault();
        BufferedReader in;
        List<String> paramList = new ArrayList<>();
        for (Map.Entry<String, String> param : params.entrySet()) {
            paramList.add(param.getKey() + "=" + param.getValue());
        }
        HttpGet httpGet = new HttpGet(httpUrl + "?" + String.join("&", paramList));
        try {
            httpGet.setHeader("Accept", "application/json");
            for (Map.Entry<String, String> headerMap : headers.entrySet()) {
                httpGet.addHeader(headerMap.getKey(), headerMap.getValue());
            }
            httpGet.setConfig(requestConfig);
            CloseableHttpResponse response = httpclient.execute(httpGet);
            InputStream content = response.getEntity().getContent();
            in = new BufferedReader(new InputStreamReader(content, StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    public static CloseableHttpClient createSSLClientDefault() {
        try {
            // SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
            // 在JSSE中,证书信任管理器类就是实现了接口X509TrustManager的类。我们可以自己实现该接口,让它信任我们指定的证书。
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            // 信任所有
            X509TrustManager x509mgr = new X509TrustManager() {
                // 该方法检查客户端的证书,若不信任该证书则抛出异常
                @Override
                public void checkClientTrusted(X509Certificate[] xcs, String string) {
                }
                // 该方法检查服务端的证书,若不信任该证书则抛出异常
                @Override
                public void checkServerTrusted(X509Certificate[] xcs, String string) {
                }
                // 返回受信任的X509证书数组。
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };
            SSLContext sslContext = SSLContext.getInstance(SSLConnectionSocketFactory.TLS);
            sslContext.init(null, new TrustManager[]{x509mgr}, null);
            // 创建HttpsURLConnection对象,并设置其SSLSocketFactory对象
            SSLConnectionSocketFactory sslSf = new SSLConnectionSocketFactory(sslContext, new DefaultHostnameVerifier());
            // HttpsURLConnection对象就可以正常连接HTTPS了,无论其证书是否经权威机构的验证,只要实现了接口X509TrustManager的类MyX509TrustManager信任该证书。
            return HttpClients.custom().setSSLSocketFactory(sslSf).build();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 创建默认的httpClient实例.
        return HttpClients.createDefault();
    }
    /**
     * 获取签名前准备参数
     *
     * @param format
     * @param timestamp
     * @return
     */
    public static String getSign(String format, long timestamp) {
        JSONObject dataSign = new JSONObject();
        dataSign.put("clientType", CLIENT_TYPE);
        dataSign.put("clientVersion", CLIENT_VERSION);
        dataSign.put("apiSecret", API_SECRET);
        dataSign.put("apiKey", API_KEY);
        dataSign.put("timestamp", timestamp);
        String formatData = getFormatData(dataSign, format);
        return md5(formatData);
    }
    /**
     * 拼接替换生成签名前字符串
     *
     * @return
     */
    public static String getFormatData(JSONObject data, String format) {
        System.err.println(data);
        System.err.println(format);
        Matcher matcher = VALUE_PATTERN.matcher(format);
        while (true) {
            while (matcher.find()) {
                String sign = matcher.group();
                String key = matcher.group(1);
                if (ObjectUtils.isNotEmpty(key)) {
                    format = format.replace(sign, data.containsKey(key) && ObjectUtils.isNotEmpty(data.getString(key)) && !"null".equalsIgnoreCase(data.getString(key)) ? data.getString(key) : "");
                } else {
                    format = format.replace(sign, "");
                }
            }
            return format;
        }
    }
    /**
     * 生成md5,小写
     *
     * @param message
     * @return
     */
    public static String md5(String message) {
        try {
            // 1 创建一个提供信息摘要算法的对象,初始化为md5算法对象
            MessageDigest md = MessageDigest.getInstance("MD5");
            // 2 将消息变成byte数组
            byte[] input = message.getBytes();
            // 3 计算后获得字节数组,这就是那128位了
            byte[] buff = md.digest(input);
            // 4 把数组每一字节(一个字节占八位)换成16进制连成md5字符串
            return byte2hex(buff);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 二进制转十六进制字符串
     *
     * @param bytes
     * @return
     */
    private static String byte2hex(byte[] bytes) {
        StringBuilder sign = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toLowerCase());
        }
        return sign.toString();
    }
    /**
     * 调用apiPost方法
     *
     * @return
     */
    public static HttpResponse doPost(String host, String path, String method,
                                      Map<String, String> headers,
                                      Map<String, String> querys,
                                      String body)
            throws Exception {
        HttpClient httpClient = wrapClient(host);
        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }
        return httpClient.execute(request);
    }
    public static HttpClient wrapClient(String host) {
        HttpClient httpClient = new DefaultHttpClient();
        if (host.startsWith("https://")) {
            sslClient(httpClient);
        }
        return httpClient;
    }
    public static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
        StringBuilder sbUrl = new StringBuilder();
        sbUrl.append(host);
        if (!StringUtils.isBlank(path)) {
            sbUrl.append(path);
        }
        if (null != querys) {
            StringBuilder sbQuery = new StringBuilder();
            for (Map.Entry<String, String> query : querys.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!StringUtils.isBlank(query.getValue())) {
                        sbQuery.append("=");
                        sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
                    }
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }
        return sbUrl.toString();
    }
    private static void sslClient(HttpClient httpClient) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                @Override
                public void checkClientTrusted(X509Certificate[] xcs, String str) {
                }
                @Override
                public void checkServerTrusted(X509Certificate[] xcs, String str) {
                }
            };
            ctx.init(null, new TrustManager[]{tm}, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = httpClient.getConnectionManager();
            SchemeRegistry registry = ccm.getSchemeRegistry();
            registry.register(new Scheme("https", 443, ssf));
        } catch (KeyManagementException ex) {
            throw new RuntimeException(ex);
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
    }
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/util/withdraw/WithdrawCallBackDTO.java
New file
@@ -0,0 +1,36 @@
package com.ruoyi.account.util.withdraw;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * 三方提现回调DTO
 */
@Data
public class WithdrawCallBackDTO {
    @ApiModelProperty(value = "数据签名,")
    private String sign;
    @ApiModelProperty(value = "打款订单号")
    private String orderCode;
    @ApiModelProperty(value = "交易流水号")
    private String tradeNo;
    @ApiModelProperty(value = "打款金额")
    private BigDecimal money;
    @ApiModelProperty(value = "订单状态 0待支付 1已支付 2处理中 4交易失败")
    private Integer status;
    @ApiModelProperty(value = "打款时间")
    private LocalDateTime time;
    @ApiModelProperty(value = "订单状态信息")
    private String message;
}
ruoyi-service/ruoyi-account/src/main/resources/mapper/account/UserWithdrawMapper.xml
New file
@@ -0,0 +1,31 @@
<?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.account.mapper.UserWithdrawMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.account.api.model.UserWithdraw">
        <id column="id" property="id" />
        <result column="app_user_id" property="appUserId" />
        <result column="money" property="money" />
        <result column="integral" property="integral" />
        <result column="audit_status" property="auditStatus" />
        <result column="audit_user_id" property="auditUserId" />
        <result column="audit_time" property="auditTime" />
        <result column="audit_msg" property="auditMsg" />
        <result column="status" property="status" />
        <result column="arrival_time" property="arrivalTime" />
        <result column="del_flag" property="delFlag" />
        <result column="create_time" property="createTime" />
    </resultMap>
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, app_user_id, money, integral, audit_status, audit_user_id, audit_time, audit_msg, status, arrival_time, del_flag, create_time
    </sql>
    <select id="page" resultType="com.ruoyi.account.api.model.UserWithdraw">
        select t1.*,t2.phone,t2.name from
                                         t_user_withdraw t1
                                         left join t_app_user t2 on t1.app_user_id = t2.id
    </select>
</mapper>