更新代码
This commit is contained in:
@@ -70,6 +70,22 @@ public class BillController {
|
||||
return ResultMessage.success(new PageResult());
|
||||
}
|
||||
|
||||
@PostMapping("/pageQueryUnpaidMargin")
|
||||
@LoginCheck
|
||||
public ResultMessage pageQueryUnpaidMargin(@RequestBody BillQueryCondition condition) {
|
||||
try {
|
||||
UserContext userContext = UserContext.get();
|
||||
if(userContext == null || userContext.getCusNo() == null) {
|
||||
return ResultMessage.success(new PageResult());
|
||||
}
|
||||
condition.setCusNo(userContext.getCusNo());
|
||||
return ResultMessage.success(billService.pageQueryUnpaidMargin(condition));
|
||||
}catch (Exception e) {
|
||||
log.error(e.getMessage(),e);
|
||||
}
|
||||
return ResultMessage.success(new PageResult());
|
||||
}
|
||||
|
||||
@LoginCheck
|
||||
@GetMapping("/countUnpayRentBills")
|
||||
public ResultMessage countUnpayRentBills() {
|
||||
@@ -85,6 +101,21 @@ public class BillController {
|
||||
return ResultMessage.success(0);
|
||||
}
|
||||
|
||||
@LoginCheck
|
||||
@GetMapping("/countUnpaidMargin")
|
||||
public ResultMessage countUnpaidRentMargin() {
|
||||
try {
|
||||
UserContext userContext = UserContext.get();
|
||||
if(userContext == null || userContext.getCusNo() == null) {
|
||||
return ResultMessage.success(0);
|
||||
}
|
||||
return ResultMessage.success(billService.countUnpaidRentMargin(userContext.getCusNo()));
|
||||
}catch (Exception e) {
|
||||
log.error(e.getMessage(),e);
|
||||
}
|
||||
return ResultMessage.success(0);
|
||||
}
|
||||
|
||||
@LoginCheck
|
||||
@GetMapping("/countUnpayWaeBills")
|
||||
public ResultMessage countUnpayWaeBills() {
|
||||
@@ -115,8 +146,19 @@ public class BillController {
|
||||
return ResultMessage.success(orderService.createOrder(orderVo));
|
||||
}catch (Exception e) {
|
||||
log.error(e.getMessage(),e);
|
||||
return ResultMessage.error(e.getMessage());
|
||||
}
|
||||
return ResultMessage.success(0);
|
||||
}
|
||||
|
||||
@PostMapping("/paycallback")
|
||||
public ResultMessage payCallBack(@RequestParam("orderId") String orderId,@RequestParam("payResult")String payResult) {
|
||||
try {
|
||||
orderService.tmPayCallback(orderId,payResult);
|
||||
return ResultMessage.success();
|
||||
}catch (Exception e) {
|
||||
log.error(e.getMessage(),e);
|
||||
}
|
||||
return ResultMessage.error();
|
||||
}
|
||||
|
||||
@LoginCheck
|
||||
@@ -133,4 +175,5 @@ public class BillController {
|
||||
}
|
||||
return ResultMessage.success(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ public class BillQueryCondition extends PageQueryRequest {
|
||||
private String cusNo; //客商编码
|
||||
private String year; //年份
|
||||
private String billStatus; //账单状态
|
||||
private String startDate; //账单开始日期
|
||||
private String endDate; //账单结束日期
|
||||
|
||||
public String getCusNo() {
|
||||
return cusNo;
|
||||
@@ -31,4 +33,20 @@ public class BillQueryCondition extends PageQueryRequest {
|
||||
public void setBillStatus(String billStatus) {
|
||||
this.billStatus = billStatus;
|
||||
}
|
||||
|
||||
public String getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public void setStartDate(String startDate) {
|
||||
this.startDate = startDate;
|
||||
}
|
||||
|
||||
public String getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public void setEndDate(String endDate) {
|
||||
this.endDate = endDate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package org.chenyon.bill;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.chenyon.oa.OaBillService;
|
||||
import org.chenyon.oa.bill.OaBillVo;
|
||||
import org.chenyon.oa.bill.OaPayRecordVo;
|
||||
import org.chenyon.pay.OrderService;
|
||||
import org.rcy.framework.api.entity.PageResult;
|
||||
import org.rcy.framework.data.id.LongIdGenerator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.IdGenerator;
|
||||
import org.springframework.util.JdkIdGenerator;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -26,17 +30,37 @@ public class BillService {
|
||||
public PageResult<OaBillVo> pageQueryWaeBill(BillQueryCondition condition) throws Exception {
|
||||
return oaBillService.pageQueryWaeBill(condition);
|
||||
}
|
||||
|
||||
public PageResult<OaBillVo> pageQueryUnpaidMargin(BillQueryCondition condition) throws Exception {
|
||||
return oaBillService.pageQueryUnpaidMargin(condition);
|
||||
}
|
||||
|
||||
public PageResult<OaPayRecordVo> pageQueryContractPayRecord(PayRecordQueryConditon condition) throws Exception {
|
||||
return oaBillService.pageQueryPayRecord(condition);
|
||||
}
|
||||
|
||||
public Integer countUnpayRentBills(String cusNo) throws Exception {
|
||||
if(StringUtils.isBlank(cusNo)){
|
||||
return 0;
|
||||
}
|
||||
Map<String,Object> params = new HashMap<>();
|
||||
params.put("cusNo",cusNo);
|
||||
return oaBillService.countUnpayRentBills(params);
|
||||
}
|
||||
|
||||
public Integer countUnpaidRentMargin(String cusNo) throws Exception {
|
||||
if(StringUtils.isBlank(cusNo)){
|
||||
return 0;
|
||||
}
|
||||
Map<String,Object> params = new HashMap<>();
|
||||
params.put("cusNo",cusNo);
|
||||
return oaBillService.countUnpaidRentMargin(params);
|
||||
}
|
||||
|
||||
public Integer countUnpayWaeBills(String cusNo) throws Exception {
|
||||
if(StringUtils.isBlank(cusNo)){
|
||||
return 0;
|
||||
}
|
||||
Map<String,Object> params = new HashMap<>();
|
||||
params.put("cusNo",cusNo);
|
||||
return oaBillService.countUnpayWaeBills(params);
|
||||
@@ -48,4 +72,8 @@ public class BillService {
|
||||
params.put("bizType",bizType);
|
||||
return oaBillService.getBillPayReceiver(params);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(LongIdGenerator.generate());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@ public final class OaApiUrl {
|
||||
* 分页查询水电费账单
|
||||
*/
|
||||
public static final String PAGE_QUERY_WAE_BILL = "/bill/waeBill/queryPage";
|
||||
/**
|
||||
* 分页查询保证金账单
|
||||
*/
|
||||
public static final String PAGE_QUERY_UNPAID_MARGIN = "/bill/unpaidMargin/queryPage";
|
||||
/**
|
||||
* 分页查询缴费记录
|
||||
*/
|
||||
@@ -69,10 +73,18 @@ public final class OaApiUrl {
|
||||
* 统计未缴租金账单数
|
||||
*/
|
||||
public static final String COUNT_UNPAY_RENT_BILLS = "/bill/countUnpay/rent";
|
||||
/**
|
||||
* 统计未缴租金账单数
|
||||
*/
|
||||
public static final String COUNT_UNPAID_RENT_MARGIN = "/bill/countUnpay/margin";
|
||||
/**
|
||||
* 获取账单收款方
|
||||
*/
|
||||
public static final String BILL_PAY_RECEIVER = "/bill/payreceiver";
|
||||
/**
|
||||
* 支付回调
|
||||
*/
|
||||
public static final String PAY_CALLBACK = "/bill/pay/callback";
|
||||
/**
|
||||
* 统计未缴水电费账单数
|
||||
*/
|
||||
|
||||
@@ -117,6 +117,9 @@ public class ContractService {
|
||||
return assetsInfo;
|
||||
}
|
||||
public Integer countUnsign(String cusNo) throws Exception {
|
||||
if(StringUtils.isBlank(cusNo)){
|
||||
return 0;
|
||||
}
|
||||
Integer count = oaContractService.countUnsign(cusNo);
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.chenyon.message;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.chenyon.wx.AesException;
|
||||
import org.rcy.framework.api.entity.PageResult;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -37,6 +38,9 @@ public class MessageService {
|
||||
}
|
||||
|
||||
public Integer countUnread(String cusNo){
|
||||
if(StringUtils.isBlank(cusNo)){
|
||||
return 0;
|
||||
}
|
||||
MessageQueryCondition messageQueryCondition = new MessageQueryCondition();
|
||||
messageQueryCondition.setMessageReceiver(cusNo);
|
||||
messageQueryCondition.setHasRead(false);
|
||||
|
||||
@@ -11,6 +11,8 @@ import org.rcy.framework.utils.json.JsonUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -40,6 +42,26 @@ public class OaBillService {
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
public PageResult<OaBillVo> pageQueryUnpaidMargin(BillQueryCondition condition) throws Exception {
|
||||
OaResp oaResp = seeyonHttpClient.sendPost("分页查询保障金账单信息", OaApiUrl.PAGE_QUERY_UNPAID_MARGIN, JsonUtils.convertJson(condition));
|
||||
String data = oaResp.getDataStr();
|
||||
Map mapRoot = JsonUtils.parseObject(data, Map.class);
|
||||
List<OaBillVo> oaBillVos = null;
|
||||
Long totalCount = 0L;
|
||||
if(mapRoot.get("totalCount") != null){
|
||||
totalCount = mapRoot.get("totalCount") instanceof Long ? (Long) mapRoot.get("totalCount") : Long.parseLong(mapRoot.get("totalCount") + "");
|
||||
}
|
||||
if(mapRoot.get("datas") != null) {
|
||||
oaBillVos = JsonUtils.parseArray(JsonUtils.convertJson(mapRoot.get("datas")), OaBillVo.class);
|
||||
}
|
||||
PageResult<OaBillVo> pageResult = new PageResult<>();
|
||||
pageResult.setPageNo(condition.getPageNo());
|
||||
pageResult.setPageSize(condition.getPageSize());
|
||||
pageResult.setRecordTotal(totalCount.intValue());
|
||||
pageResult.setResult(oaBillVos);
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
public PageResult<OaBillVo> pageQueryWaeBill(BillQueryCondition condition) throws Exception {
|
||||
OaResp oaResp = seeyonHttpClient.sendPost("分页查询水电费账单信息", OaApiUrl.PAGE_QUERY_WAE_BILL, JsonUtils.convertJson(condition));
|
||||
String data = oaResp.getDataStr();
|
||||
@@ -86,6 +108,11 @@ public class OaBillService {
|
||||
return (Integer) oaResp.getData();
|
||||
}
|
||||
|
||||
public Integer countUnpaidRentMargin(Map<String,Object> params) throws Exception {
|
||||
OaResp oaResp = seeyonHttpClient.sendPost("统计未缴保证金账单数", OaApiUrl.COUNT_UNPAID_RENT_MARGIN, JsonUtils.convertJson(params));
|
||||
return (Integer) oaResp.getData();
|
||||
}
|
||||
|
||||
public Integer countUnpayWaeBills(Map<String,Object> params) throws Exception {
|
||||
OaResp oaResp = seeyonHttpClient.sendPost("统计未缴水电费账单数", OaApiUrl.COUNT_UNPAY_WAE_BILLS, JsonUtils.convertJson(params));
|
||||
return (Integer) oaResp.getData();
|
||||
@@ -96,4 +123,11 @@ public class OaBillService {
|
||||
return (String) oaResp.getData();
|
||||
}
|
||||
|
||||
public void payCallBack(String billNo, String bizType, String payDate) throws Exception {
|
||||
Map<String,Object> params = new HashMap<>();
|
||||
params.put("billNo",billNo);
|
||||
params.put("bizType",bizType);
|
||||
params.put("payDate",payDate);
|
||||
OaResp oaResp = seeyonHttpClient.sendPost("支付回调", OaApiUrl.PAY_CALLBACK ,JsonUtils.convertJson(params));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@ public class OaCusService {
|
||||
return null;
|
||||
}
|
||||
|
||||
public CusVo matchPersonCus(String phone) throws Exception {
|
||||
public CusVo matchPersonCus(String cardNo) throws Exception {
|
||||
Map<String,Object> params = new HashMap<>();
|
||||
params.put("phone",phone);
|
||||
params.put("cardNo",cardNo);
|
||||
OaResp oaResp = seeyonHttpClient.sendPost("匹配个人客户",OaApiUrl.MATCH_PERSON, params);
|
||||
Object data = oaResp.getData();
|
||||
CusVo cusVo = new CusVo();
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -28,18 +29,27 @@ public class OaNoticeService {
|
||||
String data = oaResp.getDataStr();
|
||||
Map mapRoot = JsonUtils.parseObject(data, Map.class);
|
||||
Long totalCount = 0L;
|
||||
List<NoticeVo> vos = null;
|
||||
List<OaNoticeVo> vos = null;
|
||||
if(mapRoot.get("totalCount") != null){
|
||||
totalCount = mapRoot.get("totalCount") instanceof Long ? (Long) mapRoot.get("totalCount") : Long.parseLong(mapRoot.get("totalCount") + "");
|
||||
}
|
||||
if(mapRoot.get("datas") != null) {
|
||||
vos = JsonUtils.parseArray(JsonUtils.convertJson(mapRoot.get("datas")), NoticeVo.class);
|
||||
vos = JsonUtils.parseArray(JsonUtils.convertJson(mapRoot.get("datas")), OaNoticeVo.class);
|
||||
}
|
||||
List<NoticeVo> noticeVos = new ArrayList<>();
|
||||
PageResult<NoticeVo> pageResult = new PageResult<>();
|
||||
if(vos == null) {
|
||||
return pageResult;
|
||||
}
|
||||
for (OaNoticeVo vo : vos) {
|
||||
NoticeVo noticeVo = new NoticeVo();
|
||||
BeanUtils.copyProperties(vo, noticeVo);
|
||||
noticeVos.add(noticeVo);
|
||||
}
|
||||
pageResult.setPageNo(condition.getPageNo());
|
||||
pageResult.setPageSize(condition.getPageSize());
|
||||
pageResult.setRecordTotal(totalCount.intValue());
|
||||
pageResult.setResult(vos);
|
||||
pageResult.setResult(noticeVos);
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.chenyon.oa;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.chenyon.constants.OaApiUrl;
|
||||
import org.chenyon.fallback.FallbackSubmitVo;
|
||||
import org.chenyon.reservation.ReservationQueryCondition;
|
||||
@@ -41,6 +42,9 @@ public class OaReservationService {
|
||||
}
|
||||
|
||||
public Integer countHandling(String openId) throws Exception {
|
||||
if(StringUtils.isBlank(openId)){
|
||||
return 0;
|
||||
}
|
||||
Map<String,Object> params = new HashMap<>();
|
||||
params.put("openId",openId);
|
||||
OaResp oaResp = seeyonHttpClient.sendPost("查询处理中预约", OaApiUrl.RESERVATION_COUNT_HANDLING,JsonUtils.convertJson(params));
|
||||
|
||||
@@ -9,12 +9,15 @@ import org.chenyon.pay.shopcar.TmPayOrderRespVo;
|
||||
import org.chenyon.pay.shopcar.VaFld;
|
||||
import org.chenyon.user.UserService;
|
||||
import org.rcy.framework.data.id.LongIdGenerator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
@@ -22,6 +25,8 @@ import java.util.*;
|
||||
@Service
|
||||
public class OrderService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
|
||||
|
||||
@Autowired
|
||||
private WeAppOrderDao weAppOrderDao;
|
||||
@Autowired
|
||||
@@ -31,8 +36,10 @@ public class OrderService {
|
||||
private String mid;
|
||||
@Value("${chinaums.tid}")
|
||||
private String tid;
|
||||
@Value("${chinaums.sourceNo}")
|
||||
@Value("${chinaums.systemNo}")
|
||||
private String sourceNo;
|
||||
@Value("${chinaums.sysSource}")
|
||||
private String sysSource;
|
||||
@Value("${wx.appId}")
|
||||
private String miniProgramAppId;
|
||||
|
||||
@@ -49,94 +56,113 @@ public class OrderService {
|
||||
private String fwglMid;
|
||||
@Value("${wx.appId}")
|
||||
private String wxAppId;
|
||||
@Value("${chinaums.pay.notifyUrl}")
|
||||
private String notifyUrl;
|
||||
@Value("${cdn.domain}")
|
||||
private String domain;
|
||||
private DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/**
|
||||
* 批量创建支付订单
|
||||
* 【核心】批量前 校验所有 billNo 是否已经存在子订单
|
||||
* 子订单唯一键:billNo
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TmPayOrderRespVo createOrder(OrderVo orderVo) throws Exception {
|
||||
// 1. 基础入参校验
|
||||
// 1. 基础参数校验
|
||||
if (orderVo == null || orderVo.getBillVoList() == null || orderVo.getBillVoList().isEmpty()) {
|
||||
throw new Exception("批量支付账单不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(orderVo.getPayerOpenId())) {
|
||||
throw new Exception("支付人OPENID不能为空");
|
||||
}
|
||||
|
||||
List<UnpaidBillVo> billVoList = orderVo.getBillVoList();
|
||||
|
||||
// ======================================================================
|
||||
// 【批量支付 最强幂等校验】
|
||||
// 遍历所有子账单,根据 billNo 查询是否已存在支付记录
|
||||
// 只要有一个已存在 → 整批拒绝支付
|
||||
// 【第1步:最前置全局幂等锁单校验(核心防重,在所有操作之前!)】
|
||||
// 只要任意账单存在 【支付中未完成】订单,直接拒绝,后续所有流程全部不执行
|
||||
// 不创建本地订单、不调用银联、不生成支付单,从源头杜绝重复支付
|
||||
// ======================================================================
|
||||
for (UnpaidBillVo billVo : billVoList) {
|
||||
String billNo = billVo.getBillNo();
|
||||
// 子订单唯一键:billNo
|
||||
WeAppSubOrder existSubOrder = weAppSubOrderDao.selectByBillNo(billNo);
|
||||
if (existSubOrder != null) {
|
||||
throw new Exception("批量支付失败:账单号【" + billNo + "】已存在支付记录,禁止重复支付");
|
||||
if (existSubOrder == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 规则1:账单已支付成功,永久禁止支付
|
||||
if ("1".equals(existSubOrder.getPayStatus())) {
|
||||
throw new Exception("账单【" + billNo + "】已支付完成,无法再次支付");
|
||||
}
|
||||
|
||||
// 规则2:子订单绑定的旧主订单 处于【待支付/支付中】,直接拒绝本次请求
|
||||
Long oldMainOrderId = existSubOrder.getMainOrderId();
|
||||
WeAppOrder oldMainOrder = weAppOrderDao.get(oldMainOrderId);
|
||||
if (oldMainOrder != null && "0".equals(oldMainOrder.getPayStatus())) {
|
||||
throw new Exception("账单【" + billNo + "】存在进行中的支付订单,请等待支付结果或支付超时关闭后重试");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 生成主订单ID
|
||||
Long mainOrderId = LongIdGenerator.generate();
|
||||
String bizId = mainOrderId.toString();
|
||||
// ======================================================================
|
||||
// 【第2步:全部校验通过,生成全新主订单】
|
||||
// ======================================================================
|
||||
Long newMainOrderId = LongIdGenerator.generate();
|
||||
String bizId = String.valueOf(newMainOrderId);
|
||||
orderVo.setBizId(bizId);
|
||||
|
||||
// 3. 主订单幂等校验
|
||||
WeAppOrder existMainOrder = weAppOrderDao.findByBizId(bizId);
|
||||
if (existMainOrder != null) {
|
||||
TmPayOrderRespVo resp = new TmPayOrderRespVo();
|
||||
// resp.setOrderNo(existMainOrder.getTmOrderId());
|
||||
// resp.setAmount(existMainOrder.getAmount());
|
||||
return resp;
|
||||
}
|
||||
|
||||
// 4. 生成第三方支付订单号
|
||||
// 生成银联唯一支付单号
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
|
||||
int random = 1000000 + new Random().nextInt(9000000);
|
||||
String tmOrderNo = sourceNo + sdf.format(new Date()) + random;
|
||||
String tmOrderNo = "14BF" + sdf.format(new Date()) + random;
|
||||
|
||||
// 第三方单号唯一兜底
|
||||
WeAppOrder orderByTmNo = weAppOrderDao.selectByTmOrderId(tmOrderNo);
|
||||
if (orderByTmNo != null) {
|
||||
// 银联单号唯一性校验
|
||||
if (weAppOrderDao.selectByTmOrderId(tmOrderNo) != null) {
|
||||
throw new Exception("支付单号重复,请重新提交");
|
||||
}
|
||||
|
||||
// 5. 调用支付创建分账订单
|
||||
ShopCarOrderCreateRequest request = buildShopCarOrderCreateRequest(orderVo, tmOrderNo);
|
||||
TmPayOrderRespVo payResp = tmPayService.weChatPayCreateSplitOrder(request);
|
||||
|
||||
// 6. 批量插入子订单 + 修复 BigDecimal 金额BUG
|
||||
// ======================================================================
|
||||
// 【第3步:本地子订单、主订单全部先落库(数据先行锁定)】
|
||||
// 不存在则新增,旧单已关闭则更新绑定最新主订单
|
||||
// ======================================================================
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
for (UnpaidBillVo billVo : billVoList) {
|
||||
WeAppSubOrder subOrder = new WeAppSubOrder();
|
||||
subOrder.setMainOrderId(mainOrderId);
|
||||
subOrder.setBillNo(billVo.getBillNo()); // 唯一键
|
||||
subOrder.setAmount(billVo.getAmount());
|
||||
subOrder.setPayStatus("0");
|
||||
weAppSubOrderDao.insert(subOrder);
|
||||
String billNo = billVo.getBillNo();
|
||||
WeAppSubOrder existSub = weAppSubOrderDao.selectByBillNo(billNo);
|
||||
|
||||
if (existSub == null) {
|
||||
// 子订单首次不存在:新增,绑定本次新主订单
|
||||
WeAppSubOrder subOrder = new WeAppSubOrder();
|
||||
subOrder.setMainOrderId(newMainOrderId);
|
||||
subOrder.setBillNo(billNo);
|
||||
subOrder.setAmount(billVo.getAmount());
|
||||
subOrder.setBizSubType(billVo.getBizType());
|
||||
subOrder.setPayStatus("0");
|
||||
subOrder.setCreateTime(new Date());
|
||||
weAppSubOrderDao.insert(subOrder);
|
||||
} else {
|
||||
// 子订单已存在:走到这里已经经过前置校验
|
||||
// 旧主订单一定是【失败/关闭】状态,安全更新绑定最新主订单
|
||||
existSub.setMainOrderId(newMainOrderId);
|
||||
existSub.setPayStatus("0"); // 重置为待支付状态
|
||||
weAppSubOrderDao.updateByPrimaryKeySelective(existSub);
|
||||
}
|
||||
|
||||
// 正确累加金额(修复原BUG)
|
||||
totalAmount = totalAmount.add(new BigDecimal(billVo.getAmount()));
|
||||
}
|
||||
|
||||
// 7. 插入主订单
|
||||
// 插入全新主订单(本地数据全部落库完成)
|
||||
WeAppOrder weAppOrder = new WeAppOrder();
|
||||
weAppOrder.setId(mainOrderId);
|
||||
weAppOrder.setId(newMainOrderId);
|
||||
weAppOrder.setBizId(bizId);
|
||||
weAppOrder.setPayerId(orderVo.getPayer());
|
||||
weAppOrder.setPayerName(orderVo.getPayerName());
|
||||
weAppOrder.setTmOrderId(tmOrderNo);
|
||||
weAppOrder.setAmount(totalAmount.toString());
|
||||
weAppOrder.setPayStatus("0");
|
||||
weAppOrder.setPayStatus("0"); // 待支付(支付中)
|
||||
weAppOrder.setCreateTime(new Date());
|
||||
weAppOrder.setPayerOpenId(orderVo.getPayerOpenId());
|
||||
weAppOrderDao.insert(weAppOrder);
|
||||
|
||||
// ======================================================================
|
||||
// 【第4步:本地所有订单数据全部安全落库后,最后才调用银联创建支付单】
|
||||
// 顺序彻底修正!业务数据优先,第三方支付最后执行
|
||||
// ======================================================================
|
||||
ShopCarOrderCreateRequest request = buildShopCarOrderCreateRequest(orderVo, tmOrderNo);
|
||||
TmPayOrderRespVo payResp = tmPayService.weChatPayCreateSplitOrder(request);
|
||||
|
||||
payResp.setMainOrderId(newMainOrderId);
|
||||
return payResp;
|
||||
}
|
||||
|
||||
@@ -150,10 +176,11 @@ public class OrderService {
|
||||
request.setSubAppId(wxAppId);
|
||||
request.setMchntOrderId(orderNo);
|
||||
request.setMsgType("wx.unifiedOrder");
|
||||
String notifyUrl = domain + "/api" + "/bill/paycallback" + "?orderId=" + orderVo.getBizId() + "&" + "payResult=SUCCESS";
|
||||
request.setNotifyUrl(notifyUrl);
|
||||
request.setTradeType("MINI");
|
||||
request.setSubOpenId(orderVo.getPayerOpenId());
|
||||
request.setSysSource("OPT");
|
||||
request.setSysSource(sysSource);
|
||||
|
||||
VaFld vaFld = new VaFld();
|
||||
vaFld.setNewShopFlag("GWCXS");
|
||||
@@ -169,6 +196,9 @@ public class OrderService {
|
||||
param.put("billNo", billVo.getBillNo());
|
||||
param.put("bizType", billVo.getBizType());
|
||||
String receiver = oaBillService.getBillPayReceiver(param);
|
||||
if(StringUtils.isBlank(receiver) || "null".equals(receiver)){
|
||||
throw new RuntimeException("获取账单收款方失败,请联系管理员");
|
||||
}
|
||||
billVo.setPayee(receiver);
|
||||
|
||||
int amountCent = (int) (Double.parseDouble(billVo.getAmount()) * 100);
|
||||
@@ -184,14 +214,12 @@ public class OrderService {
|
||||
if (gyAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
SatInfo info = new SatInfo();
|
||||
info.setMerId(gyMid);
|
||||
info.setFeeBear("Y");
|
||||
info.setSatAmt(gyAmount.toString());
|
||||
satInfoList.add(info);
|
||||
}
|
||||
if (fwglAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
SatInfo info = new SatInfo();
|
||||
info.setMerId(fwglMid);
|
||||
info.setFeeBear("Y");
|
||||
info.setSatAmt(fwglAmount.toString());
|
||||
satInfoList.add(info);
|
||||
}
|
||||
@@ -202,39 +230,70 @@ public class OrderService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付回调(幂等 + 批量更新子订单)
|
||||
* 最终版支付回调(100%幂等 + 无并发问题 + 安全)
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void tmPayCallback(String tmOrderNo, String payResult) {
|
||||
WeAppOrder order = weAppOrderDao.selectByTmOrderId(tmOrderNo);
|
||||
if (order == null || "1".equals(order.getPayStatus())) {
|
||||
public void tmPayCallback(String mainOrderId, String payResult) {
|
||||
// 1. 查询主订单
|
||||
WeAppOrder order = weAppOrderDao.findByBizId(mainOrderId);
|
||||
if (order == null) {
|
||||
log.warn("[回调] 主订单不存在,bizId:{}", mainOrderId);
|
||||
return;
|
||||
}
|
||||
|
||||
// ==================== 【核心幂等】 ====================
|
||||
// 已支付 -> 直接返回,绝对不重复处理
|
||||
// ======================================================
|
||||
if ("1".equals(order.getPayStatus())) {
|
||||
log.info("[回调幂等] 订单已支付,无需重复处理,bizId:{}", mainOrderId);
|
||||
return;
|
||||
}
|
||||
|
||||
// ==================== 支付成功 ====================
|
||||
if ("SUCCESS".equalsIgnoreCase(payResult)) {
|
||||
// 更新主订单状态
|
||||
order.setPayStatus("1");
|
||||
order.setPayDate(new Date());
|
||||
weAppOrderDao.updateByPrimaryKeySelective(order);
|
||||
|
||||
// 批量更新该主订单下 所有子订单(billNo)状态
|
||||
// 查询该主订单下的所有子订单(只会查一次,因为子订单终身唯一)
|
||||
List<WeAppSubOrder> subList = weAppSubOrderDao.selectByMainOrderId(order.getId());
|
||||
|
||||
for (WeAppSubOrder sub : subList) {
|
||||
sub.setPayStatus("1");
|
||||
weAppSubOrderDao.updateById(sub);
|
||||
// ================== 子订单幂等 ==================
|
||||
if ("1".equals(sub.getPayStatus())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// 更新子订单为已支付
|
||||
sub.setPayStatus("1");
|
||||
weAppSubOrderDao.updateByPrimaryKeySelective(sub);
|
||||
String payDate = df.format(order.getPayDate() != null ? order.getPayDate() : new Date());
|
||||
try {
|
||||
TmPayOrderRespVo tmPayOrderRespVo = tmPayService.queryOrder(mid, tid, order.getTmOrderId());
|
||||
if (tmPayOrderRespVo != null && tmPayOrderRespVo.getPayTime() != null) {
|
||||
payDate = tmPayOrderRespVo.getPayTime();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.warn("[回调] 查询银联支付时间失败,使用本地时间,账单号:{}", sub.getBillNo(), ex);
|
||||
}
|
||||
// 通知OA(失败只记录,不影响支付状态)
|
||||
oaBillService.payCallBack(sub.getBillNo(), sub.getBizSubType(), payDate);
|
||||
log.info("[回调] OA通知成功,账单号:{}", sub.getBillNo());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[回调] OA通知失败,账单号:{}", sub.getBillNo(), e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("[回调] 主订单支付成功处理完成,bizId:{}", mainOrderId);
|
||||
} else {
|
||||
// ================== 支付失败 ==================
|
||||
order.setPayStatus("2");
|
||||
weAppOrderDao.updateByPrimaryKeySelective(order);
|
||||
log.info("[回调] 主订单支付失败,bizId:{}", mainOrderId);
|
||||
}
|
||||
}
|
||||
|
||||
public void callBack(String bizId) {
|
||||
WeAppOrder order = weAppOrderDao.findByBizId(bizId);
|
||||
if (order != null) {
|
||||
tmPayCallback(order.getTmOrderId(), "SUCCESS");
|
||||
}
|
||||
}
|
||||
|
||||
private UnifiedOrderRequest buildUnifiedOrderRequest(OrderVo orderVo) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
63
src/main/java/org/chenyon/pay/PayOrderCheckTask.java
Normal file
63
src/main/java/org/chenyon/pay/PayOrderCheckTask.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package org.chenyon.pay;
|
||||
|
||||
import org.chenyon.pay.shopcar.TmPayOrderRespVo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class PayOrderCheckTask {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PayOrderCheckTask.class);
|
||||
|
||||
@Autowired
|
||||
private WeAppOrderDao weAppOrderDao;
|
||||
|
||||
@Autowired
|
||||
private TmPayService tmPayService;
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
@Value("${chinaums.mid}")
|
||||
private String mid;
|
||||
@Value("${chinaums.tid}")
|
||||
private String tid;
|
||||
|
||||
/**
|
||||
* 每 5 分钟执行一次:查询支付中订单,修正状态
|
||||
*/
|
||||
@Scheduled(cron = "0 0/5 * * * ?")
|
||||
public void checkUnpaidOrder() {
|
||||
// 查询 超过 5 分钟 还在支付中的订单
|
||||
List<WeAppOrder> orderList = weAppOrderDao.selectTimeoutUnpaidOrder();
|
||||
|
||||
for (WeAppOrder order : orderList) {
|
||||
try {
|
||||
// 1. 调用银联【查询订单状态接口】
|
||||
String tmOrderId = order.getTmOrderId();
|
||||
TmPayOrderRespVo queryResult = tmPayService.queryOrder(mid, tid, tmOrderId);
|
||||
if (queryResult == null) {
|
||||
log.warn("[定时补偿] 银联查询返回空,主订单bizId:{}, tmOrderId:{}", order.getBizId(), tmOrderId);
|
||||
continue;
|
||||
}
|
||||
String status = queryResult.getStatus();
|
||||
// 2. 根据银联返回结果,修正本地订单
|
||||
if ("TRADE_SUCCESS".equals(status)) {
|
||||
// 支付成功 → 执行成功回调逻辑(核销、入账、修改状态)
|
||||
orderService.tmPayCallback(order.getBizId(), "SUCCESS");
|
||||
} else if ("TRADE_CLOSED".equals(status)) {
|
||||
// 支付失败 / 已关闭 → 修改订单状态
|
||||
orderService.tmPayCallback(order.getBizId(), "FAIL");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// 异常不影响其他订单
|
||||
log.error("[定时补偿] 处理订单异常,bizId:{}", order.getBizId(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,22 @@ package org.chenyon.pay;
|
||||
import org.chenyon.pay.shopcar.MiniPayRequest;
|
||||
import org.chenyon.pay.shopcar.ShopCarOrderCreateRequest;
|
||||
import org.chenyon.pay.shopcar.TmPayOrderRespVo;
|
||||
import org.chenyon.pay.shopcar.VaFld;
|
||||
import org.rcy.framework.utils.json.JsonUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@Service
|
||||
public class TmPayService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TmPayService.class);
|
||||
|
||||
@Autowired
|
||||
private TmHttpUtil tmHttpUtil;
|
||||
|
||||
@@ -37,17 +43,50 @@ public class TmPayService {
|
||||
}
|
||||
}
|
||||
|
||||
public TmPayOrderRespVo weChatPayCreateSplitOrder(ShopCarOrderCreateRequest request) {
|
||||
public TmPayOrderRespVo queryOrder(String vaMchntNo,String vaTermNo,String tmOrderNo) {
|
||||
//支付结果查询
|
||||
String url = "/v1/inip/upsp/shop/mini/pay-query";
|
||||
try {
|
||||
Map<String,Object> params = new HashMap<>();
|
||||
params.put("mchntOrderId",tmOrderNo);
|
||||
params.put("vaMchntNo",vaMchntNo);
|
||||
params.put("vaTermNo",vaTermNo);
|
||||
VaFld vaFld = new VaFld();
|
||||
vaFld.setNewShopFlag("");
|
||||
params.put("vaFld",vaFld);
|
||||
String paramStr = JsonUtils.convertJson(params);
|
||||
log.info("查询银联支付结果参数为: {}",paramStr );
|
||||
String respStr = tmHttpUtil.post(url, paramStr, TmHttpUtil.AuthMode.SIGNATURE);
|
||||
Map map = JsonUtils.parseObject(respStr, Map.class);
|
||||
String code = (String)map.get("respCode");
|
||||
if(!"0000".equals(code)){
|
||||
throw new RuntimeException("查询失败");
|
||||
}
|
||||
String payTime = (String)map.get("payTime");
|
||||
String status = (String)map.get("status");
|
||||
TmPayOrderRespVo tmPayOrderRespVo = new TmPayOrderRespVo();
|
||||
tmPayOrderRespVo.setPayTime(payTime);
|
||||
tmPayOrderRespVo.setStatus(status);
|
||||
return tmPayOrderRespVo;
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(),e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TmPayOrderRespVo weChatPayCreateSplitOrder(ShopCarOrderCreateRequest request) {
|
||||
String url = "/v1/inip/upsp/shop/mini/pay";
|
||||
try {
|
||||
String respStr = tmHttpUtil.post(url, JsonUtils.convertJson(request), TmHttpUtil.AuthMode.SIGNATURE);
|
||||
String paramStr = JsonUtils.convertJson(request);
|
||||
log.info("创建银联支付订单参数为: {}",paramStr );
|
||||
String respStr = tmHttpUtil.post(url, paramStr, TmHttpUtil.AuthMode.SIGNATURE);
|
||||
Map map = JsonUtils.parseObject(respStr, Map.class);
|
||||
String code = (String)map.get("respCode");
|
||||
if(!"0000".equals(code)){
|
||||
throw new RuntimeException("创建订单失败");
|
||||
}
|
||||
String forwardUrl = (String)map.get("url");
|
||||
Map<String,String> miniRequestMap = (Map<String, String>) map.get("miniPayRequest");
|
||||
Map<String,String> miniRequestMap = JsonUtils.parseObject((String)map.get("miniPayRequest"), Map.class);
|
||||
MiniPayRequest miniPayRequest = new MiniPayRequest();
|
||||
miniPayRequest.setAppId(miniRequestMap.get("appId"));
|
||||
miniPayRequest.setNonceStr(miniRequestMap.get("nonceStr"));
|
||||
|
||||
@@ -7,7 +7,7 @@ import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
@Entity
|
||||
@Table(name = "ORDER")
|
||||
@Table(name = "WEAPP_ORDER")
|
||||
public class WeAppOrder extends BaseEntity {
|
||||
|
||||
private String bizId;
|
||||
@@ -18,6 +18,7 @@ public class WeAppOrder extends BaseEntity {
|
||||
private String payStatus; //支付状态
|
||||
private String payerOpenId; //
|
||||
private String tmOrderId; //银联支付订单号
|
||||
private Date createTime;
|
||||
|
||||
|
||||
public String getBizId() {
|
||||
@@ -83,4 +84,12 @@ public class WeAppOrder extends BaseEntity {
|
||||
public void setTmOrderId(String tmOrderId) {
|
||||
this.tmOrderId = tmOrderId;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ package org.chenyon.pay;
|
||||
|
||||
import org.rcy.framework.data.dao.BaseDao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface WeAppOrderDao extends BaseDao<WeAppOrder> {
|
||||
WeAppOrder findByBizId(String bizId);
|
||||
WeAppOrder selectByTmOrderId(String tmOrderNo);
|
||||
List<WeAppOrder> selectTimeoutUnpaidOrder();
|
||||
}
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
|
||||
<mapper namespace="org.chenyon.pay.WeAppOrderDao">
|
||||
<select id="findByBizId" resultType="WeAppOrder">
|
||||
select * from order where bizId = #{bizId};
|
||||
select * from `weapp_order` where bizId = #{bizId}
|
||||
</select>
|
||||
<select id="selectByTmOrderId" resultType="WeAppOrder">
|
||||
select * from order where bizId = #{bizId};
|
||||
select * from `weapp_order` where tmOrderId = #{tmOrderNo}
|
||||
</select>
|
||||
<select id="selectTimeoutUnpaidOrder" resultType="WeAppOrder">
|
||||
select * from `weapp_order` where payStatus = '0'
|
||||
and TIMESTAMPDIFF(MINUTE, createTime, NOW()) > 5
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -4,6 +4,7 @@ import org.rcy.framework.api.entity.BaseEntity;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
@Entity
|
||||
@Table(name = "SUB_ORDER")
|
||||
@@ -13,6 +14,7 @@ public class WeAppSubOrder extends BaseEntity {
|
||||
private String amount;
|
||||
private Long mainOrderId;
|
||||
private String payStatus;
|
||||
private Date createTime;
|
||||
|
||||
public String getBillNo() {
|
||||
return billNo;
|
||||
@@ -53,4 +55,12 @@ public class WeAppSubOrder extends BaseEntity {
|
||||
public void setPayStatus(String payStatus) {
|
||||
this.payStatus = payStatus;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ public interface WeAppSubOrderDao extends BaseDao<WeAppSubOrder> {
|
||||
|
||||
WeAppSubOrder selectByBillNo(String billNo);
|
||||
|
||||
WeAppSubOrder selectByBillNoForUpdate(String billNo);
|
||||
|
||||
List<WeAppSubOrder> selectByMainOrderId(Long mainOrderId);
|
||||
|
||||
Integer updateById(WeAppSubOrder subOrder);
|
||||
|
||||
}
|
||||
|
||||
@@ -3,5 +3,13 @@
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="org.chenyon.pay.WeAppSubOrderDao">
|
||||
|
||||
<select id="selectByBillNo" resultType="WeAppSubOrder">
|
||||
select * from sub_order where billNo = #{billNo}
|
||||
</select>
|
||||
<select id="selectByBillNoForUpdate" resultType="WeAppSubOrder">
|
||||
select * from sub_order where billNo = #{billNo} FOR UPDATE
|
||||
</select>
|
||||
<select id="selectByMainOrderId" resultType="WeAppSubOrder">
|
||||
select * from sub_order where mainOrderId = #{mainOrderId}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -4,6 +4,9 @@ public class TmPayOrderRespVo {
|
||||
|
||||
private String url;
|
||||
private MiniPayRequest miniPayRequest;
|
||||
private Long mainOrderId;
|
||||
private String status;
|
||||
private String payTime;
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
@@ -20,4 +23,28 @@ public class TmPayOrderRespVo {
|
||||
public void setMiniPayRequest(MiniPayRequest miniPayRequest) {
|
||||
this.miniPayRequest = miniPayRequest;
|
||||
}
|
||||
|
||||
public Long getMainOrderId() {
|
||||
return mainOrderId;
|
||||
}
|
||||
|
||||
public void setMainOrderId(Long mainOrderId) {
|
||||
this.mainOrderId = mainOrderId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getPayTime() {
|
||||
return payTime;
|
||||
}
|
||||
|
||||
public void setPayTime(String payTime) {
|
||||
this.payTime = payTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,30 +38,54 @@ public class LoginController {
|
||||
|
||||
@PostMapping("/weChatLogin")
|
||||
public ResultMessage weChatLogin(@RequestBody LoginVo loginVo) throws Exception{
|
||||
String accessToken = weChatAccessTokenService.getAccessToken();
|
||||
// 1. 通过 code 获取微信 session_key + openid
|
||||
WeChatAuthInfo sessionInfo = weChatApi.getSessionInfo(loginVo.getLoginCode());
|
||||
|
||||
// 2. 生成第三方会话 key
|
||||
String thirdSessionKey = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
// 3. 构建 session 对象(核心:设置2小时过期)
|
||||
WxUserSession wxUserSession = new WxUserSession();
|
||||
wxUserSession.setSessionKey(sessionInfo.getSession_key());
|
||||
wxUserSession.setOpenId(sessionInfo.getOpenid());
|
||||
wxUserSession.setUnionid(sessionInfo.getUnionid());
|
||||
String thirdSessionKey = UUID.randomUUID().toString().replace("-", "");
|
||||
wxUserSession.setThirdSession(thirdSessionKey);
|
||||
String url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken;
|
||||
HttpResponse httpResponse = HttpRequestUtils.sendPost(url, "{\"code\":\"" + loginVo.getPhoneGetCode() + "\"}", null);
|
||||
String respStr = httpResponse.getRespStr();
|
||||
Map<String, Object> respMap = JsonUtils.parseObject(respStr, Map.class);
|
||||
Map<String, Object> phoneInfo = (Map<String, Object>) respMap.get("phone_info");
|
||||
User user = new User();
|
||||
if (phoneInfo != null) {
|
||||
String phoneNumber = (String) phoneInfo.get("purePhoneNumber");
|
||||
|
||||
// ===================== 关键修复 =====================
|
||||
// 过期时间:当前时间 + 2小时(微信session_key标准有效期)
|
||||
wxUserSession.setExpiresAt(System.currentTimeMillis() + 7200 * 1000);
|
||||
// ====================================================
|
||||
|
||||
// 4. 获取用户手机号(安全获取,带异常处理)
|
||||
String accessToken = weChatAccessTokenService.getAccessToken();
|
||||
String phoneNumber = null;
|
||||
try {
|
||||
String url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken;
|
||||
HttpResponse httpResponse = HttpRequestUtils.sendPost(url, "{\"code\":\"" + loginVo.getPhoneGetCode() + "\"}", null);
|
||||
String respStr = httpResponse.getRespStr();
|
||||
Map<String, Object> respMap = JsonUtils.parseObject(respStr, Map.class);
|
||||
Map<String, Object> phoneInfo = (Map<String, Object>) respMap.get("phone_info");
|
||||
if (phoneInfo != null) {
|
||||
phoneNumber = (String) phoneInfo.get("purePhoneNumber");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取微信手机号失败", e);
|
||||
}
|
||||
|
||||
// 5. 保存/更新用户
|
||||
if (phoneNumber != null) {
|
||||
User user = new User();
|
||||
user.setOpenId(sessionInfo.getOpenid());
|
||||
user.setPhone(phoneNumber);
|
||||
user.setUserType(loginVo.getLoginType());
|
||||
userService.upsert(user);
|
||||
}
|
||||
wxUserSessionService.upsertWxUserSession(wxUserSession);
|
||||
return ResultMessage.success(wxUserSession.getThirdSession());
|
||||
|
||||
// 6. 保存会话(你原来的 upsert 会自动更新)
|
||||
wxUserSessionService.upsertWxUserSession(wxUserSession);
|
||||
|
||||
// 7. 返回 thirdSession 给前端
|
||||
return ResultMessage.success(wxUserSession.getThirdSession());
|
||||
}
|
||||
|
||||
@GetMapping("/checkExpiration")
|
||||
@@ -103,9 +127,9 @@ public class LoginController {
|
||||
public ResultMessage updateVerifyCode(HttpServletRequest request,@RequestBody AuthInfoVo authInfoVo) throws Exception {
|
||||
String token = request.getHeader("WT");
|
||||
WxUserSession wxUserSession = wxUserSessionService.getSessionInfoByThirdSession(token);
|
||||
userService.updateAuthInfo(wxUserSession.getOpenId(),
|
||||
boolean authSuccess = userService.updateAuthInfo(wxUserSession.getOpenId(),
|
||||
authInfoVo.getUserType(),
|
||||
"0".equals(authInfoVo.getUserType()) ? RSAUtil.decrypt(authInfoVo.getCode(),rasPrivateKey) : authInfoVo.getCode());
|
||||
return ResultMessage.success();
|
||||
return ResultMessage.success(authSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class UserService {
|
||||
return userVo;
|
||||
}
|
||||
|
||||
public void updateAuthInfo(String openId,String userType,String verifyCode) throws Exception {
|
||||
public boolean updateAuthInfo(String openId,String userType,String verifyCode) throws Exception {
|
||||
//调用oa接口客商信息
|
||||
User user = userDao.findByOpenIdAndUserType(openId, userType);
|
||||
if(user == null) {
|
||||
@@ -76,7 +76,7 @@ public class UserService {
|
||||
}
|
||||
String cusNo = null;
|
||||
if("0".equals(userType)) {
|
||||
CusVo cusVo = oaCusService.matchPersonCus(user.getPhone());
|
||||
CusVo cusVo = oaCusService.matchPersonCus(verifyCode);
|
||||
cusNo = cusVo == null ? null : cusVo.getCusNo();
|
||||
user.setCardNo(AESUtils.encrypt(verifyCode,"abc123"));
|
||||
}else if("1".equals(user.getUserType())) {
|
||||
@@ -89,5 +89,6 @@ public class UserService {
|
||||
user.setOaAuth(true);
|
||||
userDao.updateByPrimaryKeySelective(user);
|
||||
}
|
||||
return cusNo != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,14 +53,17 @@ public class WeChatAccessTokenService {
|
||||
HttpResponse response = HttpRequestUtils.sendGet(url, headers);
|
||||
Map respMap = JsonUtils.parseObject(response.getRespStr(), Map.class);
|
||||
if (respMap.get("errcode") != null) {
|
||||
throw new IOException("获取 access_token 请求失败: " + respMap.get("errmsg"));
|
||||
log.error("获取access_token失败: {}", respMap);
|
||||
return null;
|
||||
}
|
||||
String tokenStr = respMap.get("access_token") + "";
|
||||
long ttl = Long.parseLong(respMap.get("expires_in") + "");
|
||||
// 提前 100 秒过期,更安全
|
||||
WxTokenCache tokenCache = new WxTokenCache();
|
||||
tokenCache.setToken(tokenStr);
|
||||
tokenCache.setExpireTime(now + (ttl - 20000));
|
||||
tokenCache.setExpireTime(now + (ttl - 100) * 1000);
|
||||
cache.put(appId, tokenCache);
|
||||
|
||||
return tokenStr;
|
||||
}catch (Exception e){
|
||||
log.error(e.getMessage(),e);
|
||||
|
||||
@@ -22,6 +22,8 @@ public class WxUserSession extends BaseEntity {
|
||||
|
||||
private String unionid;
|
||||
|
||||
private Long expiresAt; // 会话过期时间戳(毫秒)
|
||||
|
||||
public String getOpenId() {
|
||||
return openId;
|
||||
}
|
||||
@@ -69,4 +71,12 @@ public class WxUserSession extends BaseEntity {
|
||||
public void setUnionid(String unionid) {
|
||||
this.unionid = unionid;
|
||||
}
|
||||
|
||||
public Long getExpiresAt() {
|
||||
return expiresAt;
|
||||
}
|
||||
|
||||
public void setExpiresAt(Long expiresAt) {
|
||||
this.expiresAt = expiresAt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,28 +42,54 @@ public class WxUserSessionService {
|
||||
return wxUserSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验session是否过期 + 自动续期(纯数据库版)
|
||||
* 返回 true = 过期
|
||||
* 返回 false = 有效(且会自动续期)
|
||||
*/
|
||||
public Boolean sessionIsExpired(String thirdSession) {
|
||||
try {
|
||||
WxUserSession session = wxUserSessionDao.findByThirdSession(thirdSession);
|
||||
if(session == null) {
|
||||
return true;
|
||||
}
|
||||
String accessToken = tokenService.getAccessToken();
|
||||
String url = "https://api.weixin.qq.com/wxa/checksession?access_token=" + accessToken + "&signature=" + HmacSha256Util.hmacSha256Hex("",session.getSessionKey()) + "&openid=" + session.getOpenId() +"&sig_method=hmac_sha256";
|
||||
HttpResponse response = HttpRequestUtils.sendGet(url, null);
|
||||
if(response.getCode() != 200) {
|
||||
return true;
|
||||
}
|
||||
Map<String,Object> map = JsonUtils.parseObject(response.getRespStr(), Map.class);
|
||||
if((Integer) map.get("errcode") == 0) {
|
||||
return false;
|
||||
}else {
|
||||
return true;
|
||||
}
|
||||
}catch (Exception e) {
|
||||
log.error(e.getMessage(),e);
|
||||
if (thirdSession == null || thirdSession.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 查数据库
|
||||
WxUserSession session = wxUserSessionDao.findByThirdSession(thirdSession);
|
||||
if (session == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Long now = System.currentTimeMillis();
|
||||
Long expiresAt = session.getExpiresAt();
|
||||
|
||||
// 2. 已经过期
|
||||
if (expiresAt == null || expiresAt <= now) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===================== 自动续期逻辑 =====================
|
||||
// 规则:如果剩余时间 < 30分钟,自动续期 2小时
|
||||
long remainTime = expiresAt - now;
|
||||
long thirtyMinutes = 30 * 60 * 1000; // 30分钟
|
||||
|
||||
if (remainTime < thirtyMinutes) {
|
||||
// 新过期时间 = 当前 + 2小时
|
||||
long newExpiresAt = now + 7200 * 1000;
|
||||
session.setExpiresAt(newExpiresAt);
|
||||
|
||||
// 更新数据库
|
||||
wxUserSessionDao.updateByPrimaryKeySelective(session);
|
||||
log.info("thirdSession自动续期成功:{}", thirdSession);
|
||||
}
|
||||
// ==========================================================
|
||||
|
||||
// 3. 有效
|
||||
return false;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("session校验&续期异常", e);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public CusVo matchCus(String phone, String orgNo, String loginType) throws Exception {
|
||||
|
||||
@@ -1,19 +1,102 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ====================== 配置项(你只需要改这里)======================
|
||||
APP_NAME=/usr/local/qichenrent/qichenrent.jar
|
||||
CONFIG_DIR=file:/usr/local/qichenrent/config/
|
||||
CONFIG_DIR=/usr/local/qichenrent/config/
|
||||
LOG_DIR=/usr/local/qichenrent/logs
|
||||
PID_FILE=/usr/local/qichenrent/qichenrent.pid
|
||||
JAVA_OPTS="-Xms512m -Xmx1024m -Dfile.encoding=UTF-8"
|
||||
# ====================================================================
|
||||
|
||||
# 创建目录
|
||||
mkdir -p $LOG_DIR
|
||||
mkdir -p $CONFIG_DIR
|
||||
|
||||
echo "Starting $APP_NAME ..."
|
||||
# 获取当前PID
|
||||
get_pid() {
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
cat $PID_FILE
|
||||
fi
|
||||
}
|
||||
|
||||
nohup java $JAVA_OPTS -jar $APP_NAME \
|
||||
--spring.config.additional-location=$CONFIG_DIR \
|
||||
> $LOG_DIR/console.log 2>&1 &
|
||||
# 检查是否正在运行
|
||||
is_running() {
|
||||
pid=$(get_pid)
|
||||
if [ -n "$pid" ] && ps -p $pid > /dev/null 2>&1; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo $! > $PID_FILE
|
||||
# 启动
|
||||
start() {
|
||||
if is_running; then
|
||||
echo "应用已启动,PID=$(get_pid),无需重复启动"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Application started, PID=$(cat $PID_FILE)"
|
||||
echo "正在启动应用:$APP_NAME ..."
|
||||
|
||||
# 后台启动 SpringBoot
|
||||
nohup java $JAVA_OPTS -jar $APP_NAME \
|
||||
--spring.config.additional-location=file:$CONFIG_DIR \
|
||||
> $LOG_DIR/console.log 2>&1 &
|
||||
|
||||
# 记录PID
|
||||
echo $! > $PID_FILE
|
||||
sleep 1
|
||||
echo "应用启动成功!PID=$(get_pid)"
|
||||
}
|
||||
|
||||
# 停止
|
||||
stop() {
|
||||
if ! is_running; then
|
||||
echo "应用未运行"
|
||||
rm -f $PID_FILE
|
||||
return
|
||||
fi
|
||||
|
||||
pid=$(get_pid)
|
||||
echo "正在停止应用,PID=$pid ..."
|
||||
kill -15 $pid
|
||||
sleep 2
|
||||
|
||||
# 强制kill
|
||||
if ps -p $pid > /dev/null 2>&1; then
|
||||
kill -9 $pid
|
||||
fi
|
||||
|
||||
rm -f $PID_FILE
|
||||
echo "应用已停止"
|
||||
}
|
||||
|
||||
# 状态
|
||||
status() {
|
||||
if is_running; then
|
||||
echo "应用运行中,PID=$(get_pid)"
|
||||
else
|
||||
echo "应用未运行"
|
||||
fi
|
||||
}
|
||||
|
||||
# 命令入口
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
*)
|
||||
echo "用法:$0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user