feat:云音箱

This commit is contained in:
dzc committed 2026-05-28 14:30:06 +08:00
1 parent 434cb1b258
commit 5f343be27d
8 files changed
+316

No files matched your search

@@ -25,6 +25,8 @@ public enum HardTypeEnum {
MERCHANT_ACT("7", "商户活动"),
FARMER("8", "共富商城(助农)"),
YUN_XFJ("9", "云版消费机"),
YYX_4G("10", "4G-云音响"),
YYX_WIFI("11", "wifi-云音响"),
;
private final String type;
private final String description;
@@ -0,0 +1,26 @@
package com.bsd.pjcloud.hard.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "yyx")
@Data
public class YyxConf {
private String baseUrl;
private String username;
private String secret;
/**
* 模板
* ${price}-价格
* ${phone-last}-手机尾号
* 浦江智慧后勤,收款${price}元
*/
private String template;
}
@@ -0,0 +1,41 @@
package com.bsd.pjcloud.hard.controller.yyx;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.security.annotation.Inner;
import com.bsd.pjcloud.hard.api.entity.HardApptEntity;
import com.bsd.pjcloud.hard.service.HardApptService;
import com.bsd.pjcloud.hard.service.YyxService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 设备预约管理
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/yyx")
@Tag(description = "yyx", name = "云音箱")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class HardYyxController {
private final YyxService yyxService;
// @Operation(summary = "app预约时间列表", description = "app预约时间列表")
// @GetMapping("/send")
// @Inner(value = false)
// public R<Boolean> send() {
// yyxService.send(2030260730023755777L, 100, "17683843144");
// return R.ok(true);
// }
}
@@ -0,0 +1,10 @@
package com.bsd.pjcloud.hard.service;
/**
* 云音箱
*/
public interface YyxService {
void send(Long mchId, Integer payPrice, String phone);
}
@@ -134,6 +134,7 @@ public class HardManagerServiceImpl extends ServiceImpl<HardManagerMapper, HardM
}
dto.setHardGroupId(deptHardGroup.getHardGroupId());
dto.setDeptId(mch.getDeptId());
dto.setUniqueCode(dto.getUniqueCode());
// mq地址和端口
dto.setMqSocketAddress(mqttConfig.getAndroidAddress());
dto.setMqSocketPort(mqttConfig.getAndroidPort());
@@ -169,6 +170,7 @@ public class HardManagerServiceImpl extends ServiceImpl<HardManagerMapper, HardM
throw new RuntimeException("请设置顶级部门硬件分组");
}
dto.setDeptId(mch.getDeptId());
dto.setUniqueCode(dto.getUniqueCode());
dto.setHardGroupId(deptHardGroup.getHardGroupId());
if (!ObjectUtil.equals(dto.getMerchantId(), detail.getMerchantId())) {
// topic
@@ -175,6 +175,7 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
private final StringRedisTemplate redisTemplate;
private final HardThirdMiOrderMapper hardThirdMiOrderMapper;
private final RemoteCanteenDeliveryOrderService remoteCanteenDeliveryOrderService;
private final YyxService yyxService;
@Override
public Page<HardOrderVo> pagejoin(Page page, HardOrderDto dto) {
@@ -401,6 +402,13 @@ public class HardOrderServiceImpl extends ServiceImpl<HardOrderMapper, HardOrder
}
// 发送mq消息
getSelf().createOrderAfterSendMqMsg(orderObj.getId(), userDetail);
// 云音箱播报
String userPhone = Optional.of(userDetail).map(UserInfo::getSysUser).map(SysUser -> SysUser.getPhone()).orElse(null);
CompletableFuture.runAsync(() -> {
if (ObjectUtil.isNotEmpty(userPhone)) {
yyxService.send(orderObj.getMerchantId(), orderObj.getPayPrice(), userPhone);
}
});
return orderObj.getId();
}
@@ -0,0 +1,216 @@
package com.bsd.pjcloud.hard.service.impl.yyx;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bsd.pjcloud.hard.api.constant.HardTypeEnum;
import com.bsd.pjcloud.hard.api.entity.manager.HardManagerEntity;
import com.bsd.pjcloud.hard.config.YyxConf;
import com.bsd.pjcloud.hard.mapper.manager.HardManagerMapper;
import com.bsd.pjcloud.hard.service.YyxService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Validated
@Slf4j
@RequiredArgsConstructor
@Service
public class YyxServiceImpl implements YyxService {
private final String TAG = "4G云音箱";
private final YyxConf yyxConf;
private final HardManagerMapper hardManagerMapper;
private final StringRedisTemplate stringRedisTemplate;
private static final String YYX_TOKEN_CACHE_KEY = "hard:yyx:token";
private static final long YYX_TOKEN_EXPIRE_HOURS = 1;
@Override
public void send(Long mchId, Integer payPrice, String phone) {
try {
if (!ObjectUtil.isAllNotEmpty(mchId, payPrice, phone)) {
return;
}
// 查询商户绑定的云音箱设备
List<HardManagerEntity> yyxList = hardManagerMapper.selectList(new LambdaQueryWrapper<HardManagerEntity>().eq(HardManagerEntity::getMerchantId, mchId)
.in(HardManagerEntity::getType, List.of(HardTypeEnum.YYX_4G.getType(), HardTypeEnum.YYX_WIFI.getType()))
);
if (ObjectUtil.isEmpty(yyxList)) {
return;
}
String yuanPrice = fenToYuan(payPrice);
String lastPhone = phoneToLastPhone(phone);
yyxList.forEach(v -> {
// 4G版云音箱
if (ObjectUtil.equals(v.getType(), HardTypeEnum.YYX_4G.getType())) {
String template = yyxConf.getTemplate();
// 把`template`中的`${price}`替换为`yuanPrice`
template = template.replace("${price}", yuanPrice);
// 把`template`中的`${phone}`替换为`lastPhone`
template = template.replace("${phone-last}", lastPhone);
yyx4gHttpSend(v.getUniqueCode(), template);
}
// WIFI版云音箱
else if (ObjectUtil.equals(v.getType(), HardTypeEnum.YYX_WIFI.getType())) {
yyxWifiHttpSend(v.getUniqueCode(), "");
}
});
} catch (Exception e) {
log.error("|{}|发送失败,异常:{}", TAG, e.getMessage());
}
}
// 4G版云音箱发送语音
private boolean yyx4gHttpSend(String deviceId, String msg) {
try {
String token = getYyxGetToken();
if (ObjectUtil.isEmpty(token)) {
return false;
}
String uniqueId = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) + RandomUtil.randomString(8);
String time = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN);
HashMap<String, Object> body = new HashMap<>();
body.put("devid", deviceId);
body.put("time", time);
body.put("reqid", uniqueId);
body.put("message", msg);
// 可选参数
// body.put("type", "1");
Map<String, List<String>> headers = new HashMap<>();
headers.put("Content-Type", List.of("application/json"));
headers.put("token", List.of(token));
String res = HttpUtil.createPost(yyxConf.getBaseUrl() + "/api/message/sendMsg").body(JSONUtil.toJsonStr(body)).header(headers).execute().body();
if (ObjectUtil.isEmpty(res)) {
log.error("|{}|4G版云音箱发送语音,发送失败,返回结果错误res:|{}|", res, TAG);
}
JSON resJson = JSONUtil.parse(res);
String code = resJson.getByPath("code", String.class);
return true;
} catch (Exception e) {
e.printStackTrace();
log.error("|{}|4G版云音箱发送语音,发送失败,异常:{}", TAG, e.getMessage());
return false;
}
}
// wifi版云音箱发送语音
private boolean yyxWifiHttpSend(String deviceId, String msg) {
try {
String token = getYyxGetToken();
if (ObjectUtil.isEmpty(token)) {
return false;
}
String uniqueId = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) + RandomUtil.randomString(8);
String time = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN);
HashMap<String, Object> body = new HashMap<>();
body.put("devid", deviceId);
body.put("time", time);
body.put("reqid", uniqueId);
body.put("message", msg);
// 可选参数
// body.put("type", "1");
Map<String, List<String>> headers = new HashMap<>();
headers.put("Content-Type", List.of("application/json"));
headers.put("token", List.of(token));
String res = HttpUtil.createPost(yyxConf.getBaseUrl() + "/api/message/sendMsg").body(JSONUtil.toJsonStr(body)).header(headers).execute().body();
if (ObjectUtil.isEmpty(res)) {
log.error("|{}|获取云音箱token,发送失败,返回结果错误res:|{}|", res, TAG);
}
return true;
} catch (Exception e) {
e.printStackTrace();
log.error("|{}|wifi版云音箱发送语音,发送失败,异常:{}", TAG, e.getMessage());
return false;
}
}
// 获取云音箱token
private String getYyxGetToken() {
String cachedToken = stringRedisTemplate.opsForValue().get(YYX_TOKEN_CACHE_KEY);
if (ObjectUtil.isNotEmpty(cachedToken)) {
log.debug("|{}|从Redis缓存获取云音箱token", TAG);
return cachedToken;
}
long timestamp = System.currentTimeMillis();
String orgSign = "secret=" + yyxConf.getSecret() + "&times=" + timestamp + "&username=" + yyxConf.getUsername();
// md5加密
String digestHex = DigestUtils.md5Hex(orgSign.getBytes(StandardCharsets.UTF_8)).toUpperCase();
try {
HashMap<String, Object> body = new HashMap<>();
body.put("username", yyxConf.getUsername());
body.put("password", digestHex);
body.put("times", timestamp);
String res = HttpUtil.post(yyxConf.getBaseUrl() + "/api/getToken", JSONUtil.toJsonStr(body));
if (ObjectUtil.isEmpty(res)) {
log.error("|{}|获取云音箱token,发送失败,返回结果为空", TAG);
return null;
}
JSON resJson = JSONUtil.parse(res);
String data = resJson.getByPath("data", String.class);
if (ObjectUtil.isEmpty(data)) {
log.error("|{}|获取云音箱token,发送失败,返回结果错误res:|{}|", res, TAG);
}
JSON resData = JSONUtil.parse(data);
if (ObjectUtil.isEmpty(resData)) {
log.error("|{}|获取云音箱token,发送失败,返回结果错误res:|{}|", res, TAG);
}
String token = resData.getByPath("token", String.class);
if (ObjectUtil.isEmpty(token)) {
log.error("|{}|获取云音箱token,发送失败,返回结果错误,token为空,res:|{}|", res, TAG);
}
// redis缓存
stringRedisTemplate.opsForValue().set(YYX_TOKEN_CACHE_KEY, token, YYX_TOKEN_EXPIRE_HOURS, TimeUnit.HOURS);
log.debug("|{}|云音箱token已缓存到Redis,过期时间:{}小时", TAG, YYX_TOKEN_EXPIRE_HOURS);
return token;
} catch (Exception e) {
e.printStackTrace();
log.error("|{}|获取云音箱token,发送失败,异常:{}", TAG, e.getMessage());
return null;
}
}
private String phoneToLastPhone(String phone) {
if (ObjectUtil.isEmpty(phone)) {
return "";
}
return phone.substring(phone.length() - 4);
}
private String fenToYuan(Integer payPrice) {
if (ObjectUtil.isEmpty(payPrice)) {
return "0";
}
BigDecimal fen = new BigDecimal(payPrice);
return fen.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).toString();
}
}
@@ -52,3 +52,14 @@ mini-ali-pay:
alipay-public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqnCB+N172m5gxf1Rolfx5LErSiVwQCh0XQdDrVy6aTuVF5EtdqahSHtADyEPsBWudMBhzCfqpoZvfnvH7a+PED6uZ3DoXt7ETh8TmNM5yc+WTnD8pSphQvuHDJ3zMks489KNxiX6sXag71k9H1MXklJGPOUaljHqy/QlRn2ZLT7pFgaFH9LEcVoQBPIOurUn95078h848RFD30jZFW3fakxUGAGVVZaHH62z5OEWOmcA+CwxsEDMa1XMt02RGXgAAq4WOj+BqOGiFmhM8rnrUMchTyvx4umOzGvbF2Y9irN3aBZ5lfg+bzYcJpIOMzlM/V00LnEWzCblJzXNyH4hJQIDAQAB
callback-url: https://rj4jv5rwqs.by.takin.cc/hard/hard-ali-pay/callback
# callback-url: https://stgl.pj.gov.cn/api/hard/hard-ali-pay/callback
yyx:
base-url: https://open.sw-aiot.com
username: zianxin
secret: 67ed0e41427f754f6c7bc572b60f4162
template: 购买成功总金额{price}元
#yyx:
# base-url: https://open.sw-aiot.com
# username: ahuang
# secret: 742c07b682cd6de9851f35b80adb61fd