202506181805

This commit is contained in:
张潘 2025-06-18 18:06:00 +08:00
parent 0a8cfe078b
commit 984791f7bd
6 changed files with 1444 additions and 90 deletions

View File

@ -0,0 +1,205 @@
package com.ruoyi.system.controller;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.ControllerUtil.AppletControllerUtil;
import com.ruoyi.system.ControllerUtil.WechatPayUtil;
import com.ruoyi.system.domain.ServiceGoods;
import com.ruoyi.system.domain.GoodsOrderCursor;
import com.ruoyi.system.service.IServiceGoodsService;
import com.ruoyi.system.service.IGoodsOrderCursorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* 游标工具控制器
*
* @author Mr. Zhang Pan
* @version 1.0
* @date 2025-01-10
*/
@RestController
public class CoursorUtil extends BaseController {
@Autowired
private IServiceGoodsService serviceGoodsService;
@Autowired
private IGoodsOrderCursorService goodsOrderCursorService;
@Autowired
private WechatPayUtil wechatPayUtil;
/**
* 获取服务商品详细信息
*
* @param id 商品ID
* @param request HTTP请求对象
* @return 商品详细信息
* <p>
* 接口说明
* - 根据商品ID获取详细信息
* - type=1时返回ServiceGoodsResponse格式
* - type=2时返回json.txt中的商品格式
* - 包含图片基础信息等数组数据
*/
@GetMapping(value = "/api/service/infoData/id/{id}")
public AjaxResult serviceGoodsDataQuery(@PathVariable("id") long id, HttpServletRequest request) {
try {
// 参数验证
if (id <= 0) {
return AppletControllerUtil.appletError("商品ID无效");
}
// 查询商品信息
ServiceGoods serviceGoodsData = serviceGoodsService.selectServiceGoodsById(id);
if (serviceGoodsData == null) {
return AppletControllerUtil.appletError("商品不存在或已下架");
}
// 根据商品类型返回不同格式的数据
if (serviceGoodsData.getType() != null && serviceGoodsData.getType().intValue() == 2) {
// type=2时返回json.txt格式
Map<String, Object> goodsData = AppletControllerUtil.buildType2ServiceGoodsResponse(serviceGoodsData);
return AppletControllerUtil.appletSuccess(goodsData);
} else {
// type=1时返回ServiceGoodsResponse格式
AppletControllerUtil.ServiceGoodsResponse response = new AppletControllerUtil.ServiceGoodsResponse(serviceGoodsData);
return AppletControllerUtil.appletSuccess(response);
}
} catch (Exception e) {
return AppletControllerUtil.appletError("查询商品详情失败:" + e.getMessage());
}
}
/**
* 微信支付回调通知接口
*
* @param request HTTP请求对象
* @return XML格式的响应给微信服务器
* <p>
* 接口说明
* - 接收微信支付成功后的异步通知
* - 验证签名确保数据安全性
* - 处理支付成功后的业务逻辑
* - 返回XML格式响应告知微信处理结果
* <p>
* 微信支付回调特点
* - 微信会POST XML数据到此接口
* - 必须返回XML格式的成功/失败响应
* - 如果返回失败微信会重复发送通知
* - 建议做幂等性处理避免重复处理同一笔订单
*/
@PostMapping(value = "/api/wechatdata/pay/notify")
public String handleWechatPayNotify(HttpServletRequest request) {
try {
// 记录回调日志可选
logger.info("收到微信支付回调通知,开始处理...");
// 1. 使用WechatPayUtil处理支付回调
Map<String, Object> notifyResult = wechatPayUtil.handlePayNotify(request);
// 2. 检查处理结果
boolean success = (Boolean) notifyResult.get("success");
String message = (String) notifyResult.get("message");
if (success) {
// 3. 获取支付信息
Map<String, Object> paymentInfo = (Map<String, Object>) notifyResult.get("paymentInfo");
// 4. 处理支付成功的业务逻辑
handlePaymentSuccessLogic(paymentInfo);
// 5. 记录成功日志
logger.info("微信支付回调处理成功:订单号={}, 微信交易号={}",
paymentInfo.get("outTradeNo"), paymentInfo.get("transactionId"));
// 6. 返回成功响应给微信
return buildSuccessResponse();
} else {
// 7. 处理失败情况
logger.error("微信支付回调处理失败:{}", message);
// 8. 返回失败响应给微信
return buildFailResponse(message);
}
} catch (Exception e) {
// 9. 异常处理
logger.error("微信支付回调处理异常:", e);
// 10. 返回异常响应给微信
return buildFailResponse("处理异常:" + e.getMessage());
}
}
/**
* 处理支付成功后的业务逻辑
*
* @param paymentInfo 支付信息
*/
private void handlePaymentSuccessLogic(Map<String, Object> paymentInfo) {
try {
String outTradeNo = (String) paymentInfo.get("outTradeNo");
String transactionId = (String) paymentInfo.get("transactionId");
String totalFee = (String) paymentInfo.get("totalFee");
String timeEnd = (String) paymentInfo.get("timeEnd");
String attach = (String) paymentInfo.get("attach");
String openid = (String) paymentInfo.get("openid");
logger.info("开始处理支付成功业务逻辑:订单号={}, 金额={}", outTradeNo, totalFee);
// TODO: 根据实际业务需求实现以下逻辑
// 1. 更新订单状态为已支付
// 2. 记录支付流水
// 3. 发送支付成功通知给用户
// 4. 触发后续业务流程如发货服务安排等
// 5. 更新库存如果是商品订单
// 示例如果是商品订单临时表的支付
if (attach != null && attach.startsWith("cursor_order:")) {
String cursorOrderId = attach.replace("cursor_order:", "");
// 可以在这里处理商品订单临时表的状态更新
logger.info("处理商品订单临时表支付ID={}", cursorOrderId);
}
// 调用AppletControllerUtil的通用支付成功处理方法
AppletControllerUtil.handlePaymentSuccess(paymentInfo, false);
logger.info("支付成功业务逻辑处理完成:订单号={}", outTradeNo);
} catch (Exception e) {
logger.error("处理支付成功业务逻辑异常:", e);
// 注意即使业务逻辑处理失败也应该向微信返回成功响应
// 避免微信重复发送通知可以通过日志或其他方式处理业务异常
}
}
/**
* 构建成功响应XML
*/
private String buildSuccessResponse() {
return "<xml>" +
"<return_code><![CDATA[SUCCESS]]></return_code>" +
"<return_msg><![CDATA[OK]]></return_msg>" +
"</xml>";
}
/**
* 构建失败响应XML
*/
private String buildFailResponse(String message) {
return "<xml>" +
"<return_code><![CDATA[FAIL]]></return_code>" +
"<return_msg><![CDATA[" + message + "]]></return_msg>" +
"</xml>";
}
}

View File

@ -2833,4 +2833,188 @@ public class AppletControllerUtil {
}
return formattedDate.replace("-", "~");
}
/**
* 生成自定义前缀+16位唯一数字码
* 例如FEEB724145038871780
* @param prefix 前缀字母可为null或空自动生成4位大写字母
* @return 生成的码
*/
public static String generateCustomCode(String prefix) {
// 生成前缀
String codePrefix = prefix;
if (codePrefix == null || codePrefix.trim().isEmpty()) {
// 随机生成4位大写字母
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < 4; i++) {
char c = (char) ('A' + random.nextInt(26));
sb.append(c);
}
codePrefix = sb.toString();
}
// 生成16位唯一数字
// 用当前时间戳+3位随机数保证唯一性和随机性
long timestamp = System.currentTimeMillis();
String timePart = String.valueOf(timestamp);
// 补足到16位
StringBuilder numberPart = new StringBuilder(timePart);
Random random = new Random();
while (numberPart.length() < 16) {
numberPart.append(random.nextInt(10));
}
// 如果超出16位则截取
String finalNumber = numberPart.substring(0, 16);
return codePrefix + finalNumber;
}
/**
* 构建type=2商品的响应数据按照json.txt格式
*
* @param goods ServiceGoods商品对象
* @return 格式化的商品响应数据
*/
public static Map<String, Object> buildType2ServiceGoodsResponse(ServiceGoods goods) {
Map<String, Object> data = new HashMap<>();
// 基础信息
data.put("id", goods.getId());
data.put("title", goods.getTitle());
data.put("icon", buildImageUrl(goods.getIcon()));
data.put("sub_title", goods.getSubTitle());
data.put("info", goods.getInfo());
data.put("price", goods.getPrice() != null ? goods.getPrice().toString() : "0.00");
data.put("price_zn", goods.getPriceZn());
data.put("sales", goods.getSales() != null ? goods.getSales().intValue() : 0);
data.put("stock", goods.getStock() != null ? goods.getStock().intValue() : 0);
data.put("status", goods.getStatus() != null ? goods.getStatus() : "1");
data.put("description", goods.getDescription());
data.put("sku_type", goods.getSkuType() != null ? goods.getSkuType().intValue() : 1);
data.put("latitude", goods.getLatitude());
data.put("longitude", goods.getLongitude());
data.put("type", goods.getType() != null ? goods.getType().intValue() : 2);
data.put("cate_id", goods.getCateId());
data.put("project", goods.getProject());
data.put("sort", goods.getSort() != null ? goods.getSort().intValue() : 1);
data.put("material", goods.getMaterial());
data.put("postage", goods.getPostage() != null ? goods.getPostage().toString() : null);
data.put("basic", parseBasicStringToArray(goods.getBasic()));
data.put("margin", goods.getMargin() != null ? goods.getMargin().toString() : null);
data.put("skill_ids", goods.getSkillIds());
// 处理图片数组
data.put("imgs", parseImagesStringToArray(goods.getImgs()));
// 处理SKU数据
data.put("sku", parseSkuStringToObject(goods.getSku()));
// 时间字段
data.put("created_at", formatDateToString(goods.getCreatedAt()));
data.put("updated_at", formatDateToString(goods.getUpdatedAt()));
data.put("deleted_at", goods.getDeletedAt() != null ? formatDateToString(goods.getDeletedAt()) : null);
// 评论对象预留
data.put("comment", null);
return data;
}
/**
* 解析图片字符串为数组支持JSON和逗号分隔
*
* @param imgs 图片字符串
* @return 图片URL列表
*/
public static List<String> parseImagesStringToArray(String imgs) {
List<String> imageList = new ArrayList<>();
if (imgs != null && !imgs.trim().isEmpty()) {
try {
// 尝试解析为JSON数组
com.alibaba.fastjson2.JSONArray jsonArray = com.alibaba.fastjson2.JSONArray.parseArray(imgs);
for (int i = 0; i < jsonArray.size(); i++) {
String img = jsonArray.getString(i);
if (img != null && !img.trim().isEmpty()) {
imageList.add(buildImageUrl(img));
}
}
} catch (Exception e) {
// JSON解析失败尝试按逗号分割
String[] imgArray = imgs.split("[,]");
for (String img : imgArray) {
if (img != null && !img.trim().isEmpty()) {
imageList.add(buildImageUrl(img.trim()));
}
}
}
}
return imageList;
}
/**
* 解析基础现象字符串为数组
*
* @param basic 基础现象字符串
* @return 基础现象列表如果为空则返回null
*/
public static List<String> parseBasicStringToArray(String basic) {
if (basic == null || basic.trim().isEmpty()) {
return null;
}
List<String> basicList = new ArrayList<>();
try {
// 尝试解析为JSON数组
com.alibaba.fastjson2.JSONArray jsonArray = com.alibaba.fastjson2.JSONArray.parseArray(basic);
for (int i = 0; i < jsonArray.size(); i++) {
String item = jsonArray.getString(i);
if (item != null && !item.trim().isEmpty()) {
basicList.add(item.trim());
}
}
} catch (Exception e) {
// JSON解析失败尝试按换行或逗号分割
String[] basicArray = basic.split("[\\n,]");
for (String item : basicArray) {
if (item != null && !item.trim().isEmpty()) {
basicList.add(item.trim());
}
}
}
return basicList.isEmpty() ? null : basicList;
}
/**
* 解析SKU字符串为对象
*
* @param sku SKU字符串
* @return SKU对象解析失败返回null
*/
public static Map<String, Object> parseSkuStringToObject(String sku) {
if (sku == null || sku.trim().isEmpty()) {
return null;
}
try {
// 尝试解析为JSON对象
com.alibaba.fastjson2.JSONObject skuJson = com.alibaba.fastjson2.JSONObject.parseObject(sku);
return skuJson.toJavaObject(Map.class);
} catch (Exception e) {
// 解析失败返回null
return null;
}
}
/**
* 格式化日期为字符串
*
* @param date 日期对象
* @return 格式化后的日期字符串格式为 "yyyy-MM-dd HH:mm:ss"
*/
public static String formatDateToString(java.util.Date date) {
if (date == null) {
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
}

View File

@ -0,0 +1,41 @@
package com.ruoyi.system.ControllerUtil;
import java.util.Random;
public class GenerateCustomCode {
/**
* 生成自定义前缀+16位唯一数字码
* 例如FEEB724145038871780
* @param prefix 前缀字母可为null或空自动生成4位大写字母
* @return 生成的码
*/
public static String generCreateOrder(String prefix) {
// 生成前缀
String codePrefix = prefix;
if (codePrefix == null || codePrefix.trim().isEmpty()) {
// 随机生成4位大写字母
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < 4; i++) {
char c = (char) ('A' + random.nextInt(26));
sb.append(c);
}
codePrefix = sb.toString();
}
// 生成16位唯一数字
// 用当前时间戳+3位随机数保证唯一性和随机性
long timestamp = System.currentTimeMillis();
String timePart = String.valueOf(timestamp);
// 补足到16位
StringBuilder numberPart = new StringBuilder(timePart);
Random random = new Random();
while (numberPart.length() < 16) {
numberPart.append(random.nextInt(10));
}
// 如果超出16位则截取
String finalNumber = numberPart.substring(0, 16);
return codePrefix + finalNumber;
}
}

View File

@ -170,4 +170,126 @@ public class WXsendMsgUtil {
return PublicPush(wxMssVo);
}
//师傅设置上门费的时候的推送
public static String sendMsgForUserDoorMoney(String openid, Order order, ServiceGoods serviceGoods) throws Exception {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//拼接推送的模版
WxMssVo wxMssVo = new WxMssVo();
wxMssVo.setTouser(openid);//用户的openid要发送给那个用户通常这里应该动态传进来的
wxMssVo.setTemplate_id(ORDER_STATUS);//订阅消息模板id
wxMssVo.setPage("/pages/mine/serveOrder/details?id="+order.getId());
// String mark = order.getMark();
// if (StringUtils.isEmpty( mark)){
// mark="暂无备注";
// }
Map<String, TemplateData> m = new HashMap<>(3);
m.put("thing15", new TemplateData(serviceGoods.getTitle()));
m.put("character_string5", new TemplateData(order.getOrderId()));
m.put("date3", new TemplateData(AppletControllerUtil.timeStamp2Date(order)));
m.put("thing1", new TemplateData(order.getAddress()));
m.put("thing9", new TemplateData("师傅已设置上门费,请及时支付"));
System.out.println("师傅设置上门费的时候的推送:" + m.toString());
wxMssVo.setData(m);
return PublicPush(wxMssVo);
}
//出发上门的微信推送
public static String sendStartDoorMoney(String openid, Order order, ServiceGoods serviceGoods) throws Exception {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//拼接推送的模版
WxMssVo wxMssVo = new WxMssVo();
wxMssVo.setTouser(openid);//用户的openid要发送给那个用户通常这里应该动态传进来的
wxMssVo.setTemplate_id(ORDER_STATUS);//订阅消息模板id
wxMssVo.setPage("/pages/mine/serveOrder/details?id="+order.getId());
// String mark = order.getMark();
// if (StringUtils.isEmpty( mark)){
// mark="暂无备注";
// }
Map<String, TemplateData> m = new HashMap<>(3);
m.put("thing15", new TemplateData(serviceGoods.getTitle()));
m.put("character_string5", new TemplateData(order.getOrderId()));
m.put("date3", new TemplateData(AppletControllerUtil.timeStamp2Date(order)));
m.put("thing1", new TemplateData(order.getAddress()));
m.put("thing9", new TemplateData("师傅已经开始出发上门"));
System.out.println("师傅设置上门费的时候的推送:" + m.toString());
wxMssVo.setData(m);
return PublicPush(wxMssVo);
}
//师傅到达的微信推送
public static String sendWorkerIsComing(String openid, Order order, ServiceGoods serviceGoods) throws Exception {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//拼接推送的模版
WxMssVo wxMssVo = new WxMssVo();
wxMssVo.setTouser(openid);//用户的openid要发送给那个用户通常这里应该动态传进来的
wxMssVo.setTemplate_id(ORDER_STATUS);//订阅消息模板id
wxMssVo.setPage("/pages/mine/serveOrder/details?id="+order.getId());
// String mark = order.getMark();
// if (StringUtils.isEmpty( mark)){
// mark="暂无备注";
// }
Map<String, TemplateData> m = new HashMap<>(3);
m.put("thing15", new TemplateData(serviceGoods.getTitle()));
m.put("character_string5", new TemplateData(order.getOrderId()));
m.put("date3", new TemplateData(AppletControllerUtil.timeStamp2Date(order)));
m.put("thing1", new TemplateData(order.getAddress()));
m.put("thing9", new TemplateData("师傅已经开始出发上门"));
System.out.println("师傅设置上门费的时候的推送:" + m.toString());
wxMssVo.setData(m);
return PublicPush(wxMssVo);
}
//师傅到达的微信推送
public static String sendWorkerADDmoney(String openid, Order order, ServiceGoods serviceGoods) throws Exception {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//拼接推送的模版
WxMssVo wxMssVo = new WxMssVo();
wxMssVo.setTouser(openid);//用户的openid要发送给那个用户通常这里应该动态传进来的
wxMssVo.setTemplate_id(ORDER_STATUS);//订阅消息模板id
wxMssVo.setPage("/pages/mine/serveOrder/details?id="+order.getId());
// String mark = order.getMark();
// if (StringUtils.isEmpty( mark)){
// mark="暂无备注";
// }
Map<String, TemplateData> m = new HashMap<>(3);
m.put("thing15", new TemplateData(serviceGoods.getTitle()));
m.put("character_string5", new TemplateData(order.getOrderId()));
m.put("date3", new TemplateData(AppletControllerUtil.timeStamp2Date(order)));
m.put("thing1", new TemplateData(order.getAddress()));
m.put("thing9", new TemplateData("师傅已生成项目费用报价"));
System.out.println("师傅设置上门费的时候的推送:" + m.toString());
wxMssVo.setData(m);
return PublicPush(wxMssVo);
}
//师傅到达的微信推送
public static String sendWorkerFinishOrder(String openid, Order order, ServiceGoods serviceGoods) throws Exception {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//拼接推送的模版
WxMssVo wxMssVo = new WxMssVo();
wxMssVo.setTouser(openid);//用户的openid要发送给那个用户通常这里应该动态传进来的
wxMssVo.setTemplate_id(ORDER_STATUS);//订阅消息模板id
wxMssVo.setPage("/pages/mine/serveOrder/details?id="+order.getId());
// String mark = order.getMark();
// if (StringUtils.isEmpty( mark)){
// mark="暂无备注";
// }
Map<String, TemplateData> m = new HashMap<>(3);
m.put("thing15", new TemplateData(serviceGoods.getTitle()));
m.put("character_string5", new TemplateData(order.getOrderId()));
m.put("date3", new TemplateData(AppletControllerUtil.timeStamp2Date(order)));
m.put("thing1", new TemplateData(order.getAddress()));
m.put("thing9", new TemplateData("师傅已经已经接单"));
System.out.println("师傅设置上门费的时候的推送:" + m.toString());
wxMssVo.setData(m);
return PublicPush(wxMssVo);
}
}

View File

@ -38,6 +38,10 @@ public class YunXinPhoneUtilAPI {
private static String AXB_CALLBACK_URL = "";
private static String NOTIFY_CALLBACK_URL = "";
// 回调地址常量可通过set方法动态设置
private static String templateID = "404168";
/**
* 设置AXB绑定的回调URL
*/
@ -55,12 +59,12 @@ public class YunXinPhoneUtilAPI {
/**
* 语音通知接口调用
* @param phone 通知目标手机号
* @param templateID 语音通知模板ID
* @return VoiceResponseResult 结果对象
* @throws Exception 异常
* <p>回调地址使用 NOTIFY_CALLBACK_URL 常量如需动态设置请调用 setNotifyCallbackUrl 方法</p>
*/
public static VoiceResponseResult httpsAxbTransfer(String phone, String templateID) throws Exception {
public static VoiceResponseResult httpsAxbTransfer(String phone) throws Exception {
VoiceResponseResult resultObj = new VoiceResponseResult();
String url = NOTIFY_URL;
try {
@ -166,6 +170,7 @@ public class YunXinPhoneUtilAPI {
JSONObject rjson = JSONObject.parseObject(result);
resultObj.setResult(rjson.getString("result"));
resultObj.setMessage(rjson.getString("message"));
} catch (Exception e) {
System.out.println("[YunXinPhoneUtilAPI] 解绑异常: " + e.getMessage());
throw e;