| 2026-06-18 | Administrator | ![]() |
| 2026-06-18 | Administrator | ![]() |
| 2026-06-18 | Administrator | ![]() |
| 2026-06-18 | Administrator | ![]() |
| src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookController.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookService.java | ●●●●● patch | view | raw | blame | history | |
| src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java | ●●●●● patch | view | raw | blame | history | |
| src/main/resources/application-prod.yml | ●●●●● patch | view | raw | blame | history |
src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java
@@ -36,6 +36,7 @@ registration.excludePathPatterns("/api/fuPayReturn/callback"); registration.excludePathPatterns("/api/fuPayReturn/payment/callback"); registration.excludePathPatterns("/api/fuPay/notify"); registration.excludePathPatterns("/api/tokenview/**"); // 添加Swagger UI相关路径 registration.excludePathPatterns("/api/swagger-ui.html"); src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookController.java
New file @@ -0,0 +1,187 @@ package cc.mrbird.febs.mall.controller.dependentStation; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.HashMap; import java.util.Map; /** * Tokenview 地址监控 Webhook 接收端点 * 接收 Tokenview 推送的链上交易通知,完成充值匹配 * * @author auto-generated */ @Slf4j @RestController @RequestMapping("/api/tokenview") public class TokenviewWebhookController { @Resource private TokenviewWebhookService tokenviewWebhookService; /** * Webhook 签名密钥(HMAC-SHA256) * 需与 Tokenview 后台配置的 Secret Key 保持一致 * TODO: 移至配置文件 */ private static final String WEBHOOK_SECRET = "dd12521274e434115df5c4277755839766349007fb57936d9d5be0a7a4f0e42f"; /** * 接收 Tokenview 地址监控推送 * <p> * Tokenview 在监测到地址有交易时,会 POST JSON 到此端点。 * 需要在 Tokenview 后台配置此 URL 和 Secret Key。 * <p> * 配置步骤: * 1. 登录 Tokenview 开发者后台 * 2. 进入「地址监控」模块 * 3. 添加要监控的 TRC20 地址 * 4. 配置 Webhook URL: https://your-domain/api/tokenview/webhook * 5. 设置 Secret Key(与此处 WEBHOOK_SECRET 一致) */ @PostMapping("/webhook") public Map<String, Object> receiveWebhook(HttpServletRequest request) { Map<String, Object> result = new HashMap<>(); result.put("code", 0); result.put("msg", "ok"); try { // 1. 读取原始请求体 String rawBody = readRequestBody(request); if (StrUtil.isBlank(rawBody)) { result.put("code", 1); result.put("msg", "请求体为空"); return result; } // 2. 验证签名 String signature = request.getHeader("X-Tokenview-Signature"); if (StrUtil.isNotBlank(signature)) { if (!verifySignature(rawBody, signature)) { log.error("Webhook 签名验证失败: signature={}", signature); result.put("code", 2); result.put("msg", "签名验证失败"); return result; } } else { // 也尝试兼容其他常见签名头 signature = request.getHeader("Tokenview-Signature"); if (StrUtil.isNotBlank(signature) && !verifySignature(rawBody, signature)) { log.error("Webhook 签名验证失败: signature={}", signature); result.put("code", 2); result.put("msg", "签名验证失败"); return result; } } log.info("收到 Tokenview Webhook 推送: {}", rawBody); // 3. 解析 JSON Map<String, Object> payload = JSON.parseObject( rawBody, new TypeReference<Map<String, Object>>() {} ); if (payload == null || payload.isEmpty()) { result.put("code", 3); result.put("msg", "JSON 解析失败"); return result; } // 4. 处理充值匹配 String processResult = tokenviewWebhookService.processWebhookTransaction(payload); log.info("Webhook 处理结果: {}", processResult); if (processResult != null && processResult.startsWith("SUCCESS")) { result.put("msg", processResult); } else if (processResult != null && !processResult.startsWith("SUCCESS")) { // 非致命错误(如金额未匹配、哈希已使用等),仍返回 200,避免 Tokenview 重复推送 result.put("msg", processResult); } } catch (Exception e) { log.error("Webhook 处理异常", e); result.put("code", 500); result.put("msg", "处理异常: " + e.getMessage()); } return result; } /** * 读取请求体原始字符串 */ private String readRequestBody(HttpServletRequest request) { try { StringBuilder sb = new StringBuilder(); BufferedReader reader = request.getReader(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } catch (Exception e) { log.error("读取请求体失败", e); return null; } } /** * 验证 HMAC-SHA256 签名 * * @param payload 原始请求体 * @param signature 请求头中的签名值 * @return true-验证通过, false-验证失败 */ private boolean verifySignature(String payload, String signature) { try { Mac sha256Hmac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKey = new SecretKeySpec( WEBHOOK_SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256" ); sha256Hmac.init(secretKey); byte[] signedBytes = sha256Hmac.doFinal(payload.getBytes(StandardCharsets.UTF_8)); String expectedSignature = bytesToHex(signedBytes); // 同时尝试 Base64 编码的签名(兼容不同 Tokenview 版本) String expectedBase64 = Base64.getEncoder().encodeToString(signedBytes); boolean hexMatch = expectedSignature.equalsIgnoreCase(signature); boolean base64Match = expectedBase64.equals(signature); if (!hexMatch && !base64Match) { log.warn("签名不匹配: expected_hex={}, expected_base64={}, actual={}", expectedSignature, expectedBase64, signature); return false; } return true; } catch (Exception e) { log.error("签名验证计算异常", e); return false; } } /** * 字节数组转十六进制字符串 */ private String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02x", b)); } return sb.toString(); } } src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookService.java
New file @@ -0,0 +1,196 @@ package cc.mrbird.febs.mall.controller.dependentStation; import cc.mrbird.febs.common.enumerates.OrderStatusEnum; import cc.mrbird.febs.common.utils.RedisUtils; import cc.mrbird.febs.mall.controller.dependentStation.constant.OrderConstants; import cc.mrbird.febs.mall.controller.dependentStation.enums.SalesServiceEnums; import cc.mrbird.febs.mall.entity.DataDictionaryCustom; import cc.mrbird.febs.mall.entity.MallOrderInfo; import cc.mrbird.febs.mall.mapper.DataDictionaryCustomMapper; import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Date; import java.util.List; import java.util.Map; /** * Tokenview Webhook 充值处理服务 * 统一处理来自 webhook 推送和轮询任务的充值匹配逻辑 * * @author auto-generated */ @Slf4j @Service public class TokenviewWebhookService { @Resource private RedisUtils redisUtils; @Resource private MallOrderInfoMapper mallOrderInfoMapper; @Resource private DataDictionaryCustomMapper dataDictionaryCustomMapper; /** * 获取系统配置的 TRC20 收款地址 */ public String getReceiveAddress() { DataDictionaryCustom data = dataDictionaryCustomMapper.selectDicDataByTypeAndCode( SalesServiceEnums.TRC_ADDRESS.getType(), SalesServiceEnums.TRC_ADDRESS.getCode() ); if (data == null || StrUtil.isBlank(data.getValue())) { log.error("系统 TRC20 收款地址未配置"); return null; } return data.getValue(); } /** * 处理单笔 webhook 推送的充值交易 * * @param payload webhook 推送的 JSON 数据(已解析为 Map) * @return 处理结果描述 */ public String processWebhookTransaction(Map<String, Object> payload) { String receiveAddress = getReceiveAddress(); if (receiveAddress == null) { return "系统地址未配置,跳过处理"; } // 从 payload 提取关键字段(兼容多种 key 命名) String txid = getField(payload, "txid", "hash", "transactionHash"); String toAddress = getField(payload, "to", "toAddress"); String value = getField(payload, "value", "amount"); String tokenAddr = getField(payload, "token", "tokenAddr", "contractAddress"); String symbol = getField(payload, "symbol", "tokenSymbol"); if (StrUtil.isBlank(txid)) { log.warn("webhook 推送数据缺少交易哈希 txid: {}", payload); return "缺少 txid,跳过处理"; } // 验证收款地址是否匹配 if (StrUtil.isNotBlank(toAddress) && !receiveAddress.equalsIgnoreCase(toAddress)) { log.info("交易收款地址不匹配,期望: {}, 实际: {}, txid: {}", receiveAddress, toAddress, txid); return "收款地址不匹配,跳过处理"; } // 验证 token 合约地址(USDT: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t) if (StrUtil.isNotBlank(tokenAddr) && !"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t".equalsIgnoreCase(tokenAddr)) { log.info("非 USDT 代币交易,跳过处理: token={}, symbol={}, txid={}", tokenAddr, symbol, txid); return "非 USDT 交易,跳过处理"; } // 执行充值匹配 return matchAndUpdateOrder(txid, value, receiveAddress); } /** * 处理轮询模式下批量交易记录中的单笔 * * @param txid 交易哈希 * @param value 原始金额(需除以 1000000) * @param toAddr 收款地址(用于日志) * @return 处理结果 */ public String processPollTransaction(String txid, String value, String toAddr) { return matchAndUpdateOrder(txid, value, toAddr); } /** * 核心充值匹配逻辑: * 1. 交易哈希去重 * 2. 金额换算(除以 1000000) * 3. Redis 金额→订单映射匹配 * 4. 订单状态校验 * 5. 更新订单为待发货 */ private String matchAndUpdateOrder(String txid, String rawValue, String receiveAddress) { // 1. 交易哈希去重 List<MallOrderInfo> existOrders = mallOrderInfoMapper.selectList( Wrappers.lambdaQuery(MallOrderInfo.class) .eq(MallOrderInfo::getTradeHash, txid) ); if (CollUtil.isNotEmpty(existOrders)) { log.info("交易哈希已使用,跳过: txid={}", txid); return "HASH 已使用: " + txid; } // 2. 金额换算:除以 1000000(USDT 精度为 6) if (StrUtil.isBlank(rawValue)) { log.warn("交易金额为空: txid={}", txid); return "金额为空: " + txid; } BigDecimal amount; try { amount = new BigDecimal(rawValue) .divide(new BigDecimal("1000000"), 2, RoundingMode.DOWN); } catch (NumberFormatException e) { log.error("金额解析失败: txid={}, value={}", txid, rawValue, e); return "金额解析失败: " + txid; } // 3. Redis 金额匹配订单 String amountKey = OrderConstants.TRC20_ORDER_KEY + amount; String orderCode = redisUtils.getString(amountKey); if (StrUtil.isBlank(orderCode)) { log.info("Redis 未匹配到充值金额 {}=>{}: txid={}", amountKey, amount, txid); return "Redis 未匹配到订单: amount=" + amount + ", txid=" + txid; } // 4. 查询并校验订单 MallOrderInfo order = mallOrderInfoMapper.selectOne( Wrappers.lambdaQuery(MallOrderInfo.class) .eq(MallOrderInfo::getOrderNo, orderCode) ); if (order == null) { log.error("未找到订单: orderNo={}, txid={}", orderCode, txid); return "订单不存在: " + orderCode; } if (OrderStatusEnum.WAIT_PAY.getValue() != order.getStatus()) { log.error("订单非待支付状态: status={}, orderNo={}, txid={}", order.getStatus(), orderCode, txid); return "订单状态异常(" + order.getStatus() + "): " + orderCode; } // 5. 更新订单状态为待发货 mallOrderInfoMapper.update( null, Wrappers.lambdaUpdate(MallOrderInfo.class) .set(MallOrderInfo::getStatus, OrderStatusEnum.WAIT_SHIPPING.getValue()) .set(MallOrderInfo::getTradeHash, txid) .set(MallOrderInfo::getPayTime, new Date()) .set(MallOrderInfo::getPayResult, "1") .eq(MallOrderInfo::getId, order.getId()) ); // 删除已使用的 Redis key redisUtils.del(amountKey); log.info("Webhook 充值匹配成功: txid={}, orderNo={}, amount={}", txid, orderCode, amount); return "SUCCESS: orderNo=" + orderCode + ", amount=" + amount + ", txid=" + txid; } /** * 从 Map 中灵活获取字段值,兼容多种 key 命名 */ private String getField(Map<String, Object> map, String... keys) { for (String key : keys) { Object val = map.get(key); if (val != null) { return val.toString(); } } return null; } } src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java
@@ -1,51 +1,36 @@ package cc.mrbird.febs.mall.quartz; import cc.mrbird.febs.common.enumerates.OrderStatusEnum; import cc.mrbird.febs.common.utils.RedisUtils; import cc.mrbird.febs.mall.controller.dependentStation.constant.OrderConstants; import cc.mrbird.febs.mall.controller.dependentStation.enums.SalesServiceEnums; import cc.mrbird.febs.mall.controller.dependentStation.TokenviewWebhookService; import cc.mrbird.febs.mall.controller.dependentStation.utils.OkHttpUtil2; import cc.mrbird.febs.mall.controller.dependentStation.utils.Trc20TokenviewContentModel; import cc.mrbird.febs.mall.controller.dependentStation.utils.Trc20TokenviewModel; import cc.mrbird.febs.mall.entity.DataDictionaryCustom; import cc.mrbird.febs.mall.entity.MallOrderInfo; import cc.mrbird.febs.mall.mapper.DataDictionaryCustomMapper; import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @Component @ConditionalOnProperty(prefix = "system", name = "job", havingValue = "true") public class ChatTrc20ChargeOkLinkTask { @Resource private TokenviewWebhookService tokenviewWebhookService; @Resource private RedisUtils redisUtils; @Resource private MallOrderInfoMapper mallOrderInfoMapper; @Resource private DataDictionaryCustomMapper dataDictionaryCustomMapper; /** * 五分钟 毫秒 * 30分钟(毫秒) */ private final static long TIME_INTERVAL = 300000*6; private final static long TIME_INTERVAL = 300000 * 6; private final static String TRON_API_KEY = "X8Np5zbDuhmG6cntbhLu"; @@ -54,125 +39,83 @@ REQUEST_HEADER.put("TRON-PRO-API-KEY", TRON_API_KEY); } /** * 每分钟轮询 Tokenview API,查询 TRC20-USDT 充值记录 * 充值匹配逻辑委托给 {@link TokenviewWebhookService} */ @Scheduled(cron = "0 0/1 * * * ? ") public void recharge() { // 查询过去5分钟的记录 DataDictionaryCustom dataDictionaryCustom = dataDictionaryCustomMapper.selectDicDataByTypeAndCode( SalesServiceEnums.TRC_ADDRESS.getType(), SalesServiceEnums.TRC_ADDRESS.getCode() ); String receiveAddress = dataDictionaryCustom.getValue(); String receiveAddress = tokenviewWebhookService.getReceiveAddress(); if (receiveAddress == null) { log.error("请先配置系统地址"); return; } //https://services.tokenview.io/vipapi/trx/token/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t String url = "https://services.tokenview.io/vipapi/trx/address/tokentrans/"+receiveAddress+"/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t/1/10"; // 定时任务的执行时间和转账时间区间间隔5分钟 // 每次将上次的时间存入redis,第一次使用默认的时间 String url = "https://services.tokenview.io/vipapi/trx/address/tokentrans/" + receiveAddress + "/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t/1/10"; long endTime = System.currentTimeMillis(); long startTime = endTime-TIME_INTERVAL; long startTime = endTime - TIME_INTERVAL; System.out.println(new Date()+" 自动充值定时任务 "+startTime+" "+endTime); // 当前的充值地址 TRC20USDT_ADDRESS log.info("自动充值定时任务 startTime={}, endTime={}", startTime, endTime); Map<String, String> param = new HashMap<>(); param.put("timestampStart", startTime + ""); param.put("timestampEnd", endTime + ""); param.put("toAddress", receiveAddress); param.put("apikey", TRON_API_KEY); long l = System.currentTimeMillis(); long start = System.currentTimeMillis(); byte[] bytes = OkHttpUtil2.doGetSingle(url, REQUEST_HEADER, param, "application/json"); if (ObjectUtil.isEmpty( bytes )) { log.error("查询链上数据返回为空,传参:{}",param); if (ObjectUtil.isEmpty(bytes)) { log.error("查询链上数据返回为空, param={}", param); return; } String s = new String(bytes, StandardCharsets.UTF_8); log.info("查询到的充值记录:{}", s); Trc20TokenviewModel trc20TransfersModel = JSONObject.parseObject(s, Trc20TokenviewModel.class); List<Trc20TokenviewContentModel> tokenTransfers = trc20TransfersModel.getData(); String response = new String(bytes, StandardCharsets.UTF_8); log.info("查询到的充值记录:{}", response); Trc20TokenviewModel model = JSONObject.parseObject(response, Trc20TokenviewModel.class); List<Trc20TokenviewContentModel> tokenTransfers = model.getData(); if (CollUtil.isEmpty(tokenTransfers)) { //logger.error("FyTrc20RechargeTask查询链上数据返回没有token转账,返回结果:{}",s); return; } long l1 = System.currentTimeMillis(); log.info("查询trc耗时:{}", (l1 - l)); log.info("查询到的充值记录:{}", JSONObject.toJSONString(trc20TransfersModel)); log.info("时间区间,start:{},end:{}", startTime, endTime); // 有记录 for (Trc20TokenviewContentModel tokenTransfer : tokenTransfers) { log.info("链上时间:{}", tokenTransfer.getTime()); // 从数据库 String transactionId = tokenTransfer.getTxid(); List<MallOrderInfo> chatOrders = mallOrderInfoMapper.selectList( Wrappers.lambdaQuery(MallOrderInfo.class) .eq(MallOrderInfo::getTradeHash, transactionId) ); if(CollUtil.isNotEmpty(chatOrders)){ log.info("扫描到HASH已使用:{}",transactionId); continue; } // 金额 String quant = tokenTransfer.getValue(); BigDecimal amount = new BigDecimal(quant).divide(new BigDecimal("1000000")).setScale(2, RoundingMode.DOWN); String amountKey = OrderConstants.TRC20_ORDER_KEY + amount; String orderCode = redisUtils.getString(amountKey); if (StrUtil.isBlank(orderCode)) { log.info("Redis未扫描到充值金额:{}",transactionId); continue; } MallOrderInfo chatOrder = mallOrderInfoMapper.selectOne( Wrappers.lambdaQuery(MallOrderInfo.class) .eq(MallOrderInfo::getOrderNo, orderCode) ); if(chatOrder==null){ log.error("未找到订单:{}",orderCode); continue; } if(OrderStatusEnum.WAIT_PAY.getValue() != chatOrder.getStatus()){ log.error("订单不是待充值状态: {},订单编号:{}",transactionId,orderCode); continue; } mallOrderInfoMapper.update( null, Wrappers.lambdaUpdate(MallOrderInfo.class) .set(MallOrderInfo::getStatus, OrderStatusEnum.WAIT_SHIPPING.getValue()) .set(MallOrderInfo::getTradeHash, transactionId) .set(MallOrderInfo::getPayTime, new Date()) .set(MallOrderInfo::getPayResult, "1") .eq(MallOrderInfo::getId, chatOrder.getId()) log.info("查询耗时:{}ms, 记录数:{}, 时间区间 [{}, {}]", System.currentTimeMillis() - start, tokenTransfers.size(), startTime, endTime); for (Trc20TokenviewContentModel transfer : tokenTransfers) { log.info("链上时间: {}", transfer.getTime()); String result = tokenviewWebhookService.processPollTransaction( transfer.getTxid(), transfer.getValue(), receiveAddress ); redisUtils.del(amountKey); log.info("Redis扫描到充值记录:{},订单编号:{}",transactionId,orderCode); log.info("轮询处理结果: {}", result); } } public static void main(String[] args) { String receiveAddress = "TExto1UjtFcXKw5QdJDRqtx7wTQ15D37GD"; String url = "https://services.tokenview.io/vipapi/trx/address/tokentrans/"+receiveAddress+"/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t/1/10"; // 定时任务的执行时间和转账时间区间间隔5分钟 String url = "https://services.tokenview.io/vipapi/trx/address/tokentrans/" + receiveAddress + "/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t/1/10"; // 每次将上次的时间存入redis,第一次使用默认的时间 long endTime = System.currentTimeMillis(); long startTime = endTime-TIME_INTERVAL; long startTime = endTime - TIME_INTERVAL; System.out.println(new Date()+" 自动充值定时任务fyTrc20RechargeTokenviewTask "+startTime+" "+endTime); // 当前的充值地址 TRC20USDT_ADDRESS System.out.println(new Date() + " 自动充值定时任务 " + startTime + " " + endTime); Map<String, String> param = new HashMap<>(); param.put("timestampStart", startTime + ""); param.put("timestampEnd", endTime + ""); param.put("toAddress", receiveAddress); param.put("apikey", TRON_API_KEY); long l = System.currentTimeMillis(); byte[] bytes = OkHttpUtil2.doGetSingle(url, REQUEST_HEADER, param, "application/json"); if (bytes == null) { log.error("FyTrc20RechargeTask查询链上数据返回为空,传参:{}",param); System.out.println("查询链上数据返回为空, param=" + param); return; } src/main/resources/application-prod.yml
@@ -18,7 +18,7 @@ username: db_e2 password: db_e20806123!@# driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/db_e2?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8 url: jdbc:mysql://localhost:3306/db_e2?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8 redis: # Redis数据库索引(默认为 0) @@ -75,7 +75,7 @@ static: resource: url: http://159.198.39.140:30001/productImg/ url: https://polypepprotein.com/productImg/ path: /home/javaweb/webresource/ds/productImg/ system: