202506161649

This commit is contained in:
张潘 2025-06-16 16:50:00 +08:00
parent ac42772ffd
commit 4d768ea54e
11 changed files with 2147 additions and 878 deletions

View File

@ -163,6 +163,13 @@
<version>${velocity.version}</version>
</dependency>
<!-- 云信引入的工具包 -->
<dependency>
<groupId>com.winnerlook</groupId>
<artifactId>voice-sdk</artifactId>
<version>1.1.2</version>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>

View File

@ -58,7 +58,13 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.winnerlook</groupId>
<artifactId>voice-sdk</artifactId>
<version>1.1.2</version>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>

View File

@ -1,11 +1,13 @@
package com.ruoyi.system.ControllerUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.ruoyi.system.domain.ServiceCate;
import com.ruoyi.system.domain.ServiceGoods;
import com.ruoyi.system.domain.Users;
import com.ruoyi.system.service.IServiceGoodsService;
import com.ruoyi.system.service.IUsersService;
import com.github.pagehelper.PageInfo;
import java.util.ArrayList;
import java.util.HashMap;
@ -129,7 +131,24 @@ public class AppletControllerUtil {
result.put("data", new java.util.ArrayList<>());
return result;
}
/**
* 判断字符串是否能转换为 JSONArray
*
* @param str 需要判断的字符串
* @return 如果是合法的JSON数组字符串返回 true否则返回 false
*/
public static boolean canParseToJSONArray(String str) {
if (str == null || str.trim().isEmpty()) {
return false;
}
try {
Object obj = JSON.parse(str);
return obj instanceof JSONArray;
} catch (Exception e) {
// 解析失败不是合法的JSON数组
return false;
}
}
/**
* 服务商品详情响应实体类
*
@ -2633,4 +2652,48 @@ public class AppletControllerUtil {
return goodsData;
}
/**
* 构建分页响应结构适用于小程序worker相关接口
* @param pageInfo PageInfo对象
* @param dataList 数据列表
* @param baseUrl 分页url前缀
* @return Map分页结构
*/
public static Map<String, Object> buildPageResult(PageInfo<?> pageInfo, List<?> dataList, String baseUrl) {
Map<String, Object> dataPage = new HashMap<>();
dataPage.put("current_page", pageInfo.getPageNum());
dataPage.put("data", dataList);
dataPage.put("first_page_url", baseUrl + "?page=1");
dataPage.put("from", pageInfo.getStartRow());
dataPage.put("last_page", pageInfo.getPages());
dataPage.put("last_page_url", baseUrl + "?page=" + pageInfo.getPages());
// links
List<Map<String, Object>> links = new ArrayList<>();
Map<String, Object> prevLink = new HashMap<>();
prevLink.put("url", pageInfo.isHasPreviousPage() ? baseUrl + "?page=" + pageInfo.getPrePage() : null);
prevLink.put("label", "&laquo; Previous");
prevLink.put("active", false);
links.add(prevLink);
for (int i = 1; i <= pageInfo.getPages(); i++) {
Map<String, Object> link = new HashMap<>();
link.put("url", baseUrl + "?page=" + i);
link.put("label", String.valueOf(i));
link.put("active", i == pageInfo.getPageNum());
links.add(link);
}
Map<String, Object> nextLink = new HashMap<>();
nextLink.put("url", pageInfo.isHasNextPage() ? baseUrl + "?page=" + pageInfo.getNextPage() : null);
nextLink.put("label", "Next &raquo;");
nextLink.put("active", false);
links.add(nextLink);
dataPage.put("links", links);
dataPage.put("next_page_url", pageInfo.isHasNextPage() ? baseUrl + "?page=" + pageInfo.getNextPage() : null);
dataPage.put("path", baseUrl);
dataPage.put("per_page", pageInfo.getPageSize());
dataPage.put("prev_page_url", pageInfo.isHasPreviousPage() ? baseUrl + "?page=" + pageInfo.getPrePage() : null);
dataPage.put("to", pageInfo.getEndRow());
dataPage.put("total", pageInfo.getTotal());
return dataPage;
}
}

View File

@ -0,0 +1,75 @@
package com.ruoyi.system.ControllerUtil;
import com.winnerlook.service.https.MyVerifyHostname;
import com.winnerlook.service.https.MyX509TrustManager;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.net.URI;
public class HttpsService {
private SSLContext sslContext;
private X509TrustManager tm;
private CloseableHttpClient httpClient;
public HttpsService()
throws Exception {
sslContext = SSLContext.getInstance("TLS");
tm = new MyX509TrustManager();
sslContext.init(null, new TrustManager[]{tm},
new java.security.SecureRandom());
httpClient = HttpClients.custom()
.setSSLHostnameVerifier(new MyVerifyHostname())
.setSSLContext(sslContext).build();
}
public String doPost(String path, String headerStr,
String requestBody, String encoding) throws Exception {
String resultString = "";
CloseableHttpResponse response = null;
HttpPost httpPost = null;
try {
URI uri = new URI(path);
httpPost = new HttpPost(uri);
httpPost.setHeader("Content-type", "application/json; charset=utf-8");
/** 报文头中设置Authorization参数*/
httpPost.setHeader("Authorization", headerStr);
/** 设置请求报文体*/
httpPost.setEntity(new StringEntity(requestBody, encoding));
response = httpClient.execute(httpPost);
HttpEntity httpEntity = response.getEntity();
httpEntity = new BufferedHttpEntity(httpEntity);
resultString = EntityUtils.toString(httpEntity);
} catch (Exception e) {
throw e;
} finally { //释放连接
if (null != response)
response.close();
if (httpPost != null)
httpPost.releaseConnection();
}
return resultString;
}
}

View File

@ -223,6 +223,7 @@ public class OrderLogHandler {
updateOrderAcceptInfo(order, orderLog);
orderLog.setFirstWorkerId(orderLog.getWorkerId());
//orderLog.setWorkerId(order.getOrderLog().getWorkerId());
orderLog.setIsPause(1);
orderLog.setTitle(TITLE_3);
orderLog.setType(new BigDecimal("2.0"));

View File

@ -0,0 +1,131 @@
package com.ruoyi.system.ControllerUtil;
import com.alibaba.fastjson2.JSONObject;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;
/**
* 通用第三方接口请求工具类
* 支持GET和POST请求支持自定义Header请求体URL参数等
* 可扩展可复用适用于各种第三方接口调用场景
*/
public class PublicSendApiUtil {
/**
* 发送GET请求
* @param urlStr 请求地址
* @param headers 请求头参数可为null
* @param params URL参数可为null
* @return 响应内容
* @throws Exception 异常
*/
public static String sendGet(String urlStr, Map<String, String> headers, Map<String, String> params) throws Exception {
// 拼接URL参数
if (params != null && !params.isEmpty()) {
StringJoiner sj = new StringJoiner("&");
for (Map.Entry<String, String> entry : params.entrySet()) {
sj.add(URLEncoder.encode(entry.getKey(), "UTF-8") + "=" + URLEncoder.encode(entry.getValue(), "UTF-8"));
}
urlStr += urlStr.contains("?") ? "&" + sj.toString() : "?" + sj.toString();
}
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// 设置请求头
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
conn.setRequestProperty(entry.getKey(), entry.getValue());
}
}
conn.setDoInput(true);
// 读取响应
int code = conn.getResponseCode();
InputStream is = (code == 200) ? conn.getInputStream() : conn.getErrorStream();
StringBuilder sb = new StringBuilder();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
sb.append(new String(buf, 0, len, StandardCharsets.UTF_8));
}
is.close();
return sb.toString();
}
/**
* 发送POST请求
* @param urlStr 请求地址
* @param headers 请求头参数可为null
* @param params URL参数可为null
* @param body 请求体内容可为null通常为JSON字符串或表单数据
* @return 响应内容
* @throws Exception 异常
*/
public static String sendPost(String urlStr, Map<String, String> headers, Map<String, String> params, String body) throws Exception {
// 拼接URL参数
if (params != null && !params.isEmpty()) {
StringJoiner sj = new StringJoiner("&");
for (Map.Entry<String, String> entry : params.entrySet()) {
sj.add(URLEncoder.encode(entry.getKey(), "UTF-8") + "=" + URLEncoder.encode(entry.getValue(), "UTF-8"));
}
urlStr += urlStr.contains("?") ? "&" + sj.toString() : "?" + sj.toString();
}
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
// 设置请求头
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
conn.setRequestProperty(entry.getKey(), entry.getValue());
}
}
conn.setDoOutput(true);
conn.setDoInput(true);
// 写入请求体
if (body != null) {
try (OutputStream os = conn.getOutputStream()) {
os.write(body.getBytes(StandardCharsets.UTF_8));
}
}
System.out.println(conn);
// 读取响应
int code = conn.getResponseCode();
InputStream is = (code == 000000) ? conn.getInputStream() : conn.getErrorStream();
StringBuilder sb = new StringBuilder();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
sb.append(new String(buf, 0, len, StandardCharsets.UTF_8));
}
is.close();
return sb.toString();
}
/**
* 示例如何使用该工具类发送GET和POST请求
*/
public static void main(String[] args) throws Exception {
// GET请求示例
Map<String, String> headers = new HashMap<>();
headers.put("Accept", "application/json");
Map<String, String> params = new HashMap<>();
params.put("q", "test");
String getResult = sendGet("https://httpbin.org/get", headers, params);
System.out.println("GET结果" + getResult);
// POST请求示例
headers.put("Content-Type", "application/json");
String postBody = "{\"name\":\"张三\"}";
String postResult = sendPost("https://httpbin.org/post", headers, null, postBody);
System.out.println("POST结果" + postResult);
}
}

View File

@ -0,0 +1,100 @@
package com.ruoyi.system.ControllerUtil;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
/**
* 微信小程序通知消息推送工具类
* 支持获取access_token发送订阅消息等功能
* 适用于ControllerService等各层直接调用
*/
public class WXsendMsgUtil {
// 微信小程序的AppID和AppSecret请替换为你自己的
private static final String APPID = "YOUR_APPID";
private static final String APPSECRET = "YOUR_APPSECRET";
/**
* 获取微信小程序全局唯一后台接口调用凭据 access_token
* @return access_token字符串
* @throws Exception 异常
*/
public static String getAccessToken() throws Exception {
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + APPSECRET;
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
InputStream is = conn.getInputStream();
StringBuilder sb = new StringBuilder();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
sb.append(new String(buf, 0, len, StandardCharsets.UTF_8));
}
is.close();
JSONObject json = JSONObject.parseObject(sb.toString());
return json.getString("access_token");
}
/**
* 发送微信小程序订阅消息
* @param accessToken access_token
* @param touser 接收者openid
* @param templateId 模板ID
* @param page 跳转页面可选
* @param data 模板内容Map<String, Object>{"thing1":{"value":"内容"}}
* @return 微信接口响应
* @throws Exception 异常
*/
public static String sendSubscribeMsg(String accessToken, String touser, String templateId, String page, Map<String, Object> data) throws Exception {
String url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken;
Map<String, Object> param = new HashMap<>();
param.put("touser", touser);
param.put("template_id", templateId);
if (page != null && !page.isEmpty()) {
param.put("page", page);
}
param.put("data", data);
String body = JSON.toJSONString(param);
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
os.write(body.getBytes(StandardCharsets.UTF_8));
}
InputStream is = conn.getInputStream();
StringBuilder sb = new StringBuilder();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
sb.append(new String(buf, 0, len, StandardCharsets.UTF_8));
}
is.close();
return sb.toString();
}
/**
* 示例如何推送微信小程序订阅消息
*/
public static void main(String[] args) throws Exception {
// 1. 获取access_token
String accessToken = getAccessToken();
// 2. 组装消息内容
Map<String, Object> data = new HashMap<>();
Map<String, String> thing1 = new HashMap<>();
thing1.put("value", "测试内容");
data.put("thing1", thing1);
// 3. 发送消息
String result = sendSubscribeMsg(accessToken, "OPENID", "TEMPLATE_ID", "pages/index/index", data);
System.out.println("推送结果:" + result);
}
}

View File

@ -0,0 +1,187 @@
package com.ruoyi.system.ControllerUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.winnerlook.model.*;
import com.winnerlook.util.Base64;
import com.winnerlook.util.MD5Util;
import java.util.LinkedHashMap;
/**
* 云信小号相关API工具类
* <p>
* 封装了云信小号的AXAXB绑定解绑语音通知等接口的调用方法
* 统一了签名鉴权请求体组装等流程便于业务系统直接调用
* </p>
* <p>
* 依赖
* - fastjson2 用于JSON序列化/反序列化
* - com.winnerlook.model.* 需包含请求/响应体相关POJO
* - com.winnerlook.util.Base64MD5Util 用于加密
* </p>
* <p>
* 使用前请确保相关依赖和配置已正确引入
* </p>
*/
public class YunXinPhoneUtilAPI {
private static final String APP_ID = "256339";
private static final String TOKEN = "ecf5ae04c52b4d85a0e4255d7d0ebb71";
// 云信接口地址常量
private static final String UNBIND_URL = "https://101.37.133.245:11008/voice/1.0.0/middleNumberUnbind";
private static final String NOTIFY_URL = "https://101.37.133.245:11008/voice/1.0.0/notify";
private static final String AXB_URL = "https://101.37.133.245:11008/voice/1.0.0/middleNumberAXB";
// 回调地址常量可通过set方法动态设置
private static String AXB_CALLBACK_URL = "";
private static String NOTIFY_CALLBACK_URL = "";
/**
* 设置AXB绑定的回调URL
*/
public static void setAxbCallbackUrl(String callbackUrl) {
AXB_CALLBACK_URL = callbackUrl;
}
/**
* 设置语音通知的回调URL
*/
public static void setNotifyCallbackUrl(String callbackUrl) {
NOTIFY_CALLBACK_URL = callbackUrl;
}
/**
* 语音通知接口调用
* @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 {
VoiceResponseResult resultObj = new VoiceResponseResult();
String url = NOTIFY_URL;
try {
// 组装请求体
VoiceNotifyBody transfer = new VoiceNotifyBody();
transfer.setCalleeNumber(phone);
transfer.setTemplateId(templateID);
transfer.setTemplateArgs(new LinkedHashMap<String, String>()); // 可根据需要传递参数
transfer.setCallbackUrl(NOTIFY_CALLBACK_URL);
// 获取当前时间戳
long timestamp = System.currentTimeMillis();
// 生成鉴权参数
String authorization = Base64.encodeBase64(APP_ID + ":" + timestamp);
String sig = MD5Util.getMD5(APP_ID + TOKEN + timestamp);
// 拼接最终URL
url = url + "/" + APP_ID + "/" + sig;
String body = JSON.toJSONString(transfer);
// 调用HTTPS服务
HttpsService httpsService = new HttpsService();
String result = httpsService.doPost(url, authorization, body, "UTF-8");
JSONObject rjson = JSONObject.parseObject(result);
resultObj.setCallId(rjson.getString("callId"));
resultObj.setResult(rjson.getString("result"));
resultObj.setMessage(rjson.getString("message"));
} catch (Exception e) {
System.out.println("[YunXinPhoneUtilAPI] 语音通知异常: " + e.getMessage());
throw e;
}
return resultObj;
}
/**
* AXB绑定接口调用
* 提前绑定小号号码号码A号码B的关系AB呼叫小号即可互相通话
* @param minphone 隐私小号X号
* @param phoneA 号码A
* @param phoneB 号码B
* @return VoiceResponseResult 结果对象
* @throws Exception 异常
* <p>回调地址使用 AXB_CALLBACK_URL 常量如需动态设置请调用 setAxbCallbackUrl 方法</p>
*/
public static VoiceResponseResult httpsPrivacyBindAxb(String minphone, String phoneA, String phoneB) throws Exception {
String url = AXB_URL;
VoiceResponseResult resultObj = new VoiceResponseResult();
try {
PrivacyBindBodyAxb bindBodyAxb = new PrivacyBindBodyAxb();
// 组装请求体
bindBodyAxb.setMiddleNumber(minphone);
bindBodyAxb.setBindNumberA(phoneA);
bindBodyAxb.setBindNumberB(phoneB);
bindBodyAxb.setCallRec(0); // 1:开启录音0:关闭
bindBodyAxb.setMaxBindingTime(3600); // 有效时长为空表示永久
bindBodyAxb.setPassthroughCallerToA(0); // 0:不透传; 1:透传
bindBodyAxb.setPassthroughCallerToB(0); // 0:不透传; 1:透传
bindBodyAxb.setCallbackUrl(AXB_CALLBACK_URL);
long timestamp = System.currentTimeMillis();
String authorization = Base64.encodeBase64(APP_ID + ":" + timestamp);
String sig = MD5Util.getMD5(APP_ID + TOKEN + timestamp);
url = url + "/" + APP_ID + "/" + sig;
String body = JSON.toJSONString(bindBodyAxb);
HttpsService httpsService = new HttpsService();
String result = httpsService.doPost(url, authorization, body, "UTF-8");
JSONObject rjson = JSONObject.parseObject(result);
resultObj.setCallId(rjson.getString("callId"));
resultObj.setResult(rjson.getString("result"));
resultObj.setMessage(rjson.getString("message"));
System.out.println(result);
} catch (Exception e) {
System.out.println("[YunXinPhoneUtilAPI] AXB绑定异常: " + e.getMessage());
throw e;
}
return resultObj;
}
/**
* 解绑接口调用
* 解绑指定小号与AB号码的绑定关系
* @param phoneA 号码A
* @param phoneB 号码B
* @param phone 隐私小号X号
* @return VoiceResponseResult 结果对象
* @throws Exception 异常
*/
public static VoiceResponseResult httpsPrivacyUnbind(String phoneA, String phoneB, String phone) throws Exception {
VoiceResponseResult resultObj = new VoiceResponseResult();
String url = UNBIND_URL;
try {
PrivacyUnbindBody unbindBody = new PrivacyUnbindBody();
long timestamp = System.currentTimeMillis();
unbindBody.setMiddleNumber(phone);
unbindBody.setBindNumberA(phoneA);
unbindBody.setBindNumberB(phoneB);
String authorization = Base64.encodeBase64(APP_ID + ":" + timestamp);
String sig = MD5Util.getMD5(APP_ID + TOKEN + timestamp);
url = url + "/" + APP_ID + "/" + sig;
String body = JSON.toJSONString(unbindBody);
HttpsService httpsService = new HttpsService();
String result = httpsService.doPost(url, authorization, body, "UTF-8");
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;
}
return resultObj;
}
/**
* main方法示例解绑操作
*/
public static void main(String[] args) throws Exception {
// 设置回调地址如有需要
setAxbCallbackUrl("http://your-callback-url.com/axb");
setNotifyCallbackUrl("http://your-callback-url.com/notify");
// 示例解绑操作
VoiceResponseResult res = httpsPrivacyUnbind("18339212639", "15270824290", "15695650664");
System.out.println("解绑结果:" + JSON.toJSONString(res));
}
}

View File

@ -84,7 +84,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectOrderLogByOrderId" parameterType="String" resultMap="OrderLogResult">
select * from order_log where order_id = #{orderId}
select * from order_log where order_id = #{orderId} order by id DESC
</select>
<insert id="insertOrderLog" parameterType="OrderLog" useGeneratedKeys="true" keyProperty="id">

View File

@ -25,7 +25,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="goodId != null and goodId != ''"> and good_id like concat('%', #{goodId}, '%')</if>
<if test="typeId != null and typeId != ''">
and type_id in (select b.id from quote_material_type b where b.title like concat('%', #{typeId}, '%') ) </if>
and type_id like concat('%', #{typeId}, '%') </if>
<if test="title != null and title != ''"> and title like concat('%', #{title}, '%')</if>
<if test="priceMin != null and priceMax != null">