Compare commits

...

3 Commits

Author SHA1 Message Date
科技小王子 b46341bd32 refactor(shop): 优化商城信息获取和缓存逻辑 3 days ago
科技小王子 210efaf470 refactor(wx-login): 重构微信登录和小程序码生成逻辑 3 days ago
科技小王子 3546be75e4 删除测试文件 5 days ago
  1. 33
      quick_payment_check.sql
  2. 1
      src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImpl.java
  3. 17
      src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImplHelper.java
  4. 238
      src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java
  5. 10
      src/main/java/com/gxwebsoft/common/system/service/SettingService.java
  6. 53
      src/main/java/com/gxwebsoft/common/system/service/impl/SettingServiceImpl.java
  7. 87
      src/main/java/com/gxwebsoft/house/service/impl/HouseInfoServiceImpl.java
  8. 1
      src/main/java/com/gxwebsoft/shop/controller/ShopMainController.java
  9. 5
      src/main/java/com/gxwebsoft/shop/service/impl/ShopWebsiteServiceImpl.java
  10. 18
      src/main/java/com/gxwebsoft/shop/vo/MenuVo.java
  11. 182
      src/test/java/com/gxwebsoft/shop/OrderQueryTest.java

33
quick_payment_check.sql

@ -1,33 +0,0 @@
-- 快速支付配置检查SQL
-- 请在数据库中执行此查询
SELECT
'=== 支付配置检查 ===' as title,
tenant_id as '租户ID',
CASE
WHEN mch_id IS NULL OR mch_id = '' THEN '❌ 未配置'
ELSE CONCAT('', mch_id)
END as '商户号',
CASE
WHEN app_id IS NULL OR app_id = '' THEN '❌ 未配置'
ELSE CONCAT('', app_id)
END as '应用ID',
CASE
WHEN merchant_serial_number IS NULL OR merchant_serial_number = '' THEN '❌ 未配置'
ELSE '✅ 已配置'
END as '证书序列号',
CASE
WHEN api_key IS NULL OR api_key = '' THEN '❌ 未配置'
WHEN LENGTH(api_key) != 32 THEN CONCAT('❌ 长度错误(', LENGTH(api_key), '位)')
ELSE '✅ 已配置(32位)'
END as 'API密钥',
CASE
WHEN status = 1 THEN '✅ 已启用'
ELSE '❌ 未启用'
END as '状态'
FROM sys_payment
WHERE type = 0 -- 微信支付
ORDER BY tenant_id;
-- 如果上面的查询没有返回结果,说明没有微信支付配置,请执行:
-- SELECT COUNT(*) as '微信支付配置数量' FROM sys_payment WHERE type = 0;

1
src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImpl.java

@ -334,6 +334,7 @@ public class CmsWebsiteServiceImpl extends ServiceImpl<CmsWebsiteMapper, CmsWebs
// 从数据库获取站点信息
CmsWebsite website = getWebsiteFromDatabase(tenantId);
if (website == null) {
throw new RuntimeException("请先创建站点");
}

17
src/main/java/com/gxwebsoft/cms/service/impl/CmsWebsiteServiceImplHelper.java

@ -119,6 +119,7 @@ public class CmsWebsiteServiceImplHelper {
/**
* 转换导航列表为VO
* 整理导航栏目录结构(ShopInfo)
*/
public static List<MenuVo> convertNavigationToVO(List<CmsNavigation> navigations) {
if (navigations == null) {
@ -127,18 +128,18 @@ public class CmsWebsiteServiceImplHelper {
return navigations.stream().map(nav -> {
MenuVo navVO = new MenuVo();
navVO.setNavigationId(nav.getNavigationId());
navVO.setNavigationName(nav.getTitle()); // 修复:使用 title 字段
navVO.setNavigationUrl(nav.getPath()); // 修复:使用 path 字段
navVO.setNavigationIcon(nav.getIcon()); // 修复:使用 icon 字段
navVO.setNavigationColor(nav.getColor()); // 修复:使用 color 字段
navVO.setId(nav.getNavigationId());
navVO.setName(nav.getTitle());
navVO.setPath(nav.getPath());
navVO.setIcon(nav.getIcon());
navVO.setColor(nav.getColor());
navVO.setParentId(nav.getParentId());
navVO.setSort(nav.getSortNumber()); // 修复:使用 sortNumber 字段
navVO.setSort(nav.getSortNumber());
navVO.setHide(nav.getHide());
navVO.setTop(nav.getTop());
// 安全转换 target 字段:将字符串值映射为整数
navVO.setPath(nav.getPath());
navVO.setTarget(convertTargetToInteger(nav.getTarget()));
navVO.setNavigationType(nav.getModel()); // 修复:使用 model 字段
navVO.setType(nav.getModel());
// 递归处理子导航
if (nav.getChildren() != null) {

238
src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java

@ -38,6 +38,7 @@ import java.io.File;
import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.gxwebsoft.common.core.constants.PlatformConstants.MP_WEIXIN;
@ -224,7 +225,7 @@ public class WxLoginController extends BaseController {
// 获取openid
private JSONObject getOpenIdByCode(UserParam userParam) {
// 获取微信小程序配置信息
JSONObject setting = settingService.getBySettingKey("mp-weixin");
JSONObject setting = settingService.getBySettingKey("mp-weixin",getTenantId());
// 获取openId
String apiUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + setting.getString("appId") + "&secret=" + setting.getString("appSecret") + "&js_code=" + userParam.getCode() + "&grant_type=authorization_code";
// 执行get请求
@ -277,24 +278,34 @@ public class WxLoginController extends BaseController {
* 获取接口调用凭据AccessToken
* <a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html">...</a>
*/
private String getAccessToken() {
String key = ACCESS_TOKEN_KEY.concat(":").concat(getTenantId().toString());
// 获取微信小程序配置信息
JSONObject setting = settingService.getBySettingKey("mp-weixin");
public String getAccessToken() {
Integer tenantId = getTenantId();
String key = ACCESS_TOKEN_KEY.concat(":").concat(tenantId.toString());
// 使用跨租户方式获取微信小程序配置信息
JSONObject setting = settingService.getBySettingKeyIgnoreTenant("mp-weixin", tenantId);
if (setting == null) {
throw new BusinessException("请先配置小程序");
}
// 从缓存获取access_token
String value = redisTemplate.opsForValue().get(key);
if (value != null) {
// 解析access_token
JSONObject response = JSON.parseObject(value);
// return response.getString("access_token");
String accessToken = response.getString("access_token");
if (accessToken != null) {
return accessToken;
}
}
// 微信获取凭证接口
String apiUrl = "https://api.weixin.qq.com/cgi-bin/token";
// 组装url参数
String url = apiUrl.concat("?grant_type=client_credential").concat("&appid=").concat(setting.getString("appId")).concat("&secret=").concat(setting.getString("appSecret"));
String url = apiUrl.concat("?grant_type=client_credential")
.concat("&appid=").concat(setting.getString("appId"))
.concat("&secret=").concat(setting.getString("appSecret"));
// 执行get请求
String result = HttpUtil.get(url);
// 解析access_token
@ -321,7 +332,7 @@ public class WxLoginController extends BaseController {
// 请求微信接口获取openid
String apiUrl = "https://api.weixin.qq.com/sns/jscode2session";
final HashMap<String, Object> map = new HashMap<>();
final JSONObject setting = settingService.getBySettingKey("mp-weixin");
final JSONObject setting = settingService.getBySettingKey("mp-weixin",getTenantId());
final String appId = setting.getString("appId");
final String appSecret = setting.getString("appSecret");
map.put("appid", appId);
@ -349,7 +360,7 @@ public class WxLoginController extends BaseController {
String apiUrl = "https://api.weixin.qq.com/sns/jscode2session";
final HashMap<String, Object> map = new HashMap<>();
final JSONObject setting = settingService.getBySettingKey("mp-weixin");
final JSONObject setting = settingService.getBySettingKey("mp-weixin",getTenantId());
final String appId = setting.getString("appId");
final String appSecret = setting.getString("appSecret");
map.put("appid", appId);
@ -408,29 +419,62 @@ public class WxLoginController extends BaseController {
}
@Operation(summary = "获取微信小程序码-订单核销码-数量极多的业务场景")
@GetMapping("/getOrderQRCodeUnlimited/{orderNo}")
public void getOrderQRCodeUnlimited(@PathVariable("orderNo") String orderNo, HttpServletResponse response) throws IOException {
System.out.println("orderNo = " + orderNo);
String apiUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + getLocalAccessToken();
final HashMap<String, Object> map = new HashMap<>();
map.put("scene", orderNo);
map.put("page", "pages/index/index");
map.put("env_version", "develop");
String jsonBody = JSON.toJSONString(map);
System.out.println("请求的 JSON body = " + jsonBody);
// 获取图片 Buffer
byte[] qrCode = HttpRequest.post(apiUrl)
.body(JSON.toJSONString(map))
.execute().bodyBytes();
@GetMapping("/getOrderQRCodeUnlimited/{scene}")
public void getOrderQRCodeUnlimited(@PathVariable("scene") String scene, HttpServletResponse response) throws IOException {
System.out.println("scene = " + scene);
try {
// 使用统一的 access_token 获取方法
String accessToken = getAccessToken();
String apiUrl = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken;
final HashMap<String, Object> map = new HashMap<>();
map.put("scene", scene);
map.put("page", "pages/index/index");
map.put("env_version", "trial");
String jsonBody = JSON.toJSONString(map);
System.out.println("请求的 JSON body = " + jsonBody);
// 获取微信 API 响应
cn.hutool.http.HttpResponse httpResponse = HttpRequest.post(apiUrl)
.body(jsonBody)
.execute();
byte[] responseBytes = httpResponse.bodyBytes();
String contentType = httpResponse.header("Content-Type");
// 检查响应内容类型,判断是否为错误响应
if (contentType != null && contentType.contains("application/json")) {
// 微信返回了错误信息(JSON格式)
String errorResponse = new String(responseBytes, "UTF-8");
System.err.println("微信 API 错误响应: " + errorResponse);
// 返回错误信息给前端
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write(errorResponse);
return;
}
// 成功获取二维码图片
response.setContentType("image/png");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Content-Disposition", "inline; filename=qrcode.png");
// 输出图片
response.getOutputStream().write(responseBytes);
System.out.println("二维码生成成功,大小: " + responseBytes.length + " bytes");
// 设置响应头
response.setContentType("image/png");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Content-Disposition", "inline; filename=encrypted_qrcode.png");
} catch (Exception e) {
System.err.println("生成二维码失败: " + e.getMessage());
e.printStackTrace();
// 输出图片
response.getOutputStream().write(qrCode);
System.out.println("response = " + response);
// 返回错误信息
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("{\"error\":\"生成二维码失败: " + e.getMessage() + "\"}");
}
}
@Operation(summary = "获取微信小程序码-用户ID")
@ -474,31 +518,48 @@ public class WxLoginController extends BaseController {
/** 获取/刷新 access_token */
public String getLocalAccessToken() throws IOException {
long now = Instant.now().getEpochSecond();
String key ="AccessToken:Local:10550";
if (redisUtil.get(key) != null && now < tokenExpireEpoch - 60) {
return redisUtil.get(key);
}
HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/cgi-bin/token")
.newBuilder()
.addQueryParameter("grant_type", "client_credential")
.addQueryParameter("appid", "wx51962d6ac21f2ed2")
.addQueryParameter("secret", "d821f98de8a6c1ba7bc7e0ee84bcbc8e")
.build();
Request req = new Request.Builder().url(url).get().build();
try (Response resp = http.newCall(req).execute()) {
String body = resp.body().string();
JsonNode json = om.readTree(body);
if (json.has("access_token")) {
String token = json.get("access_token").asText();
long expiresIn = json.get("expires_in").asInt(7200);
redisUtil.set(key,token,expiresIn,TimeUnit.SECONDS);
tokenExpireEpoch = now + expiresIn;
return token;
} else {
throw new IOException("Get access_token failed: " + body);
long now = Instant.now().getEpochSecond();
String key = "AccessToken:Local:" + getTenantId();
if (redisUtil.get(key) != null && now < tokenExpireEpoch - 60) {
return redisUtil.get(key);
}
// 使用跨租户方式获取微信小程序配置信息
Integer tenantId = getTenantId();
JSONObject setting = settingService.getBySettingKeyIgnoreTenant("mp-weixin", tenantId);
if (setting == null) {
throw new IOException("请先配置小程序");
}
String appId = setting.getString("appId");
String appSecret = setting.getString("appSecret");
if (appId == null || appSecret == null) {
throw new IOException("小程序配置不完整,缺少 appId 或 appSecret");
}
HttpUrl url = HttpUrl.parse("https://api.weixin.qq.com/cgi-bin/token")
.newBuilder()
.addQueryParameter("grant_type", "client_credential")
.addQueryParameter("appid", appId)
.addQueryParameter("secret", appSecret)
.build();
Request req = new Request.Builder().url(url).get().build();
try (Response resp = http.newCall(req).execute()) {
String body = resp.body().string();
JsonNode json = om.readTree(body);
if (json.has("access_token")) {
String token = json.get("access_token").asText();
long expiresIn = json.get("expires_in").asInt(7200);
redisUtil.set(key, token, expiresIn, TimeUnit.SECONDS);
tokenExpireEpoch = now + expiresIn;
return token;
} else {
throw new IOException("Get access_token failed: " + body);
}
}
}
}
/**
@ -508,5 +569,74 @@ public class WxLoginController extends BaseController {
return config.getUploadPath() + "file/";
}
@Operation(summary = "调试:检查微信小程序配置")
@GetMapping("/debug/checkWxConfig")
public ApiResult<?> debugCheckWxConfig() {
Integer tenantId = getTenantId();
Map<String, Object> result = new HashMap<>();
result.put("tenantId", tenantId);
try {
// 尝试获取配置
JSONObject setting = settingService.getBySettingKeyIgnoreTenant("mp-weixin", tenantId);
result.put("hasConfig", true);
result.put("config", setting);
} catch (Exception e) {
result.put("hasConfig", false);
result.put("error", e.getMessage());
// 提供创建配置的建议
Map<String, Object> suggestion = new HashMap<>();
suggestion.put("message", "请在系统设置中创建微信小程序配置");
suggestion.put("configKey", "mp-weixin");
suggestion.put("tenantId", tenantId);
suggestion.put("sampleConfig", createSampleWxConfig());
result.put("suggestion", suggestion);
}
return success("配置检查完成", result);
}
@Operation(summary = "调试:创建示例微信小程序配置")
@PostMapping("/debug/createSampleWxConfig")
public ApiResult<?> debugCreateSampleWxConfig(@RequestBody Map<String, String> params) {
Integer tenantId = getTenantId();
String appId = params.get("appId");
String appSecret = params.get("appSecret");
if (appId == null || appSecret == null) {
return fail("请提供 appId 和 appSecret", null);
}
try {
// 创建配置对象
Setting setting = new Setting();
setting.setSettingKey("mp-weixin");
setting.setTenantId(tenantId);
// 创建配置内容
Map<String, String> config = new HashMap<>();
config.put("appId", appId);
config.put("appSecret", appSecret);
setting.setContent(JSON.toJSONString(config));
setting.setComments("微信小程序配置");
setting.setSortNumber(1);
// 保存配置
settingService.save(setting);
return success("微信小程序配置创建成功", setting);
} catch (Exception e) {
return fail("创建配置失败: " + e.getMessage(), null);
}
}
private Map<String, String> createSampleWxConfig() {
Map<String, String> sample = new HashMap<>();
sample.put("appId", "wx_your_app_id_here");
sample.put("appSecret", "your_app_secret_here");
return sample;
}
}

10
src/main/java/com/gxwebsoft/common/system/service/SettingService.java

@ -46,7 +46,15 @@ public interface SettingService extends IService<Setting> {
* @param key key
* @return Setting
*/
JSONObject getBySettingKey(String key);
JSONObject getBySettingKey(String key,Integer tenantId);
/**
* 跨租户获取设置内容
* @param key 设置键
* @param tenantId 租户ID
* @return JSONObject
*/
JSONObject getBySettingKeyIgnoreTenant(String key, Integer tenantId);
Setting getData(String settingKey);

53
src/main/java/com/gxwebsoft/common/system/service/impl/SettingServiceImpl.java

@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.common.core.annotation.IgnoreTenant;
import com.gxwebsoft.common.core.config.ConfigProperties;
import com.gxwebsoft.common.core.exception.BusinessException;
import com.gxwebsoft.common.core.web.PageParam;
@ -16,6 +17,7 @@ import com.gxwebsoft.common.system.service.SettingService;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAConfig;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@ -44,8 +46,10 @@ public class SettingServiceImpl extends ServiceImpl<SettingMapper, Setting> impl
@Value("${spring.profiles.active:prod}")
private String activeProfile;
@Autowired
private SettingService settingService;
@Override
@Override
public PageResult<Setting> pageRel(SettingParam param) {
PageParam<Setting, SettingParam> page = new PageParam<>(param);
//page.setDefaultOrder("create_time desc");
@ -70,7 +74,10 @@ public class SettingServiceImpl extends ServiceImpl<SettingMapper, Setting> impl
}
@Override
public JSONObject getBySettingKey(String key) {
public JSONObject getBySettingKey(String key, Integer tenantId) {
System.out.println("tenantId = " + tenantId);
final JSONObject settingKey = settingService.getBySettingKey("setting_key", tenantId);
System.out.println("settingKey = " + settingKey);
Setting setting = this.getOne(new QueryWrapper<Setting>().eq("setting_key", key), false);
System.out.println("setting1 = " + setting);
if(setting == null){
@ -99,6 +106,48 @@ public class SettingServiceImpl extends ServiceImpl<SettingMapper, Setting> impl
return JSON.parseObject(setting.getContent());
}
@Override
@IgnoreTenant("跨租户获取指定租户的设置配置")
public JSONObject getBySettingKeyIgnoreTenant(String key, Integer tenantId) {
System.out.println("跨租户查询设置 - key: " + key + ", tenantId: " + tenantId);
final List<Setting> list = list(new LambdaQueryWrapper<Setting>().eq(Setting::getTenantId, tenantId));
System.out.println("list = " + list);
// 使用跨租户查询,指定租户ID
Setting setting = this.getOne(new QueryWrapper<Setting>()
.eq("setting_key", key)
.eq("tenant_id", tenantId), false);
System.out.println("跨租户查询结果: " + setting);
if(setting == null){
if ("mp-weixin".equals(key)) {
throw new BusinessException("租户 " + tenantId + " 的小程序未配置,请先在系统设置中配置微信小程序信息");
}
if ("payment".equals(key)) {
throw new BusinessException("租户 " + tenantId + " 的支付未配置");
}
if ("sms".equals(key)) {
throw new BusinessException("租户 " + tenantId + " 的短信未配置");
}
if ("wx-work".equals(key)){
throw new BusinessException("租户 " + tenantId + " 的企业微信未配置");
}
if ("setting".equals(key)) {
throw new BusinessException("租户 " + tenantId + " 的基本信息未配置");
}
if ("wx-official".equals(key)) {
throw new BusinessException("租户 " + tenantId + " 的微信公众号未配置");
}
if ("printer".equals(key)) {
throw new BusinessException("租户 " + tenantId + " 的打印机未配置");
}
throw new BusinessException("租户 " + tenantId + " 的配置项 " + key + " 未找到");
}
return JSON.parseObject(setting.getContent());
}
@Override
public Setting getData(String settingKey) {
return query().eq("setting_key", settingKey).one();

87
src/main/java/com/gxwebsoft/house/service/impl/HouseInfoServiceImpl.java

@ -16,6 +16,7 @@ import com.gxwebsoft.common.core.config.ConfigProperties;
import com.gxwebsoft.common.core.utils.CommonUtil;
import com.gxwebsoft.common.core.utils.ImageUtil;
import com.gxwebsoft.common.core.utils.RedisUtil;
import com.gxwebsoft.common.system.controller.WxLoginController;
import com.gxwebsoft.common.system.service.SettingService;
import com.gxwebsoft.house.mapper.HouseInfoMapper;
import com.gxwebsoft.house.service.HouseInfoService;
@ -57,6 +58,8 @@ public class HouseInfoServiceImpl extends ServiceImpl<HouseInfoMapper, HouseInfo
@Resource
private RedisUtil redisUtil;
@Resource
private WxLoginController wxLoginController;
private static final String ACCESS_TOKEN_KEY = "cache:wx:access_token";
@ -230,7 +233,7 @@ public class HouseInfoServiceImpl extends ServiceImpl<HouseInfoMapper, HouseInfo
*/
private String generateMiniProgramQRCode(Integer houseId) {
try {
String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + getAccessToken();
String apiUrl = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + wxLoginController.getAccessToken();
final HashMap<String, Object> map = new HashMap<>();
// 设置小程序页面路径:sub_pages/house/detail/ + houseId
map.put("path", "sub_pages/house/detail/?houseId=" + houseId);
@ -269,47 +272,47 @@ public class HouseInfoServiceImpl extends ServiceImpl<HouseInfoMapper, HouseInfo
*
* @return access_token
*/
private String getAccessToken() {
String key = ACCESS_TOKEN_KEY.concat(":").concat("1"); // 这里可以根据实际情况获取tenantId
System.out.println("key = " + key);
// 获取微信小程序配置信息
JSONObject setting = settingService.getBySettingKey("mp-weixin");
if (setting == null) {
throw new RuntimeException("请先配置小程序");
}
// 从缓存获取access_token
String value = redisUtil.get(key);
System.out.println("redisTemplate-value = " + value);
if (value != null) {
JSONObject response = JSON.parseObject(value);
String accessToken = response.getString("access_token");
if (StrUtil.isNotBlank(accessToken)) {
return accessToken;
}
}
// 微信获取凭证接口
String apiUrl = "https://api.weixin.qq.com/cgi-bin/token";
// 组装url参数
String url = apiUrl.concat("?grant_type=client_credential")
.concat("&appid=").concat(setting.getString("appId"))
.concat("&secret=").concat(setting.getString("appSecret"));
// 执行get请求
String result = cn.hutool.http.HttpUtil.get(url);
System.out.println("获取access_token结果: " + result);
// 解析access_token
JSONObject response = JSON.parseObject(result);
if (response.getString("access_token") != null) {
// 存入缓存
redisUtil.set(key, result, 7000L, TimeUnit.SECONDS);
return response.getString("access_token");
}
throw new RuntimeException("小程序配置不正确");
}
// private String getAccessToken() {
// String key = ACCESS_TOKEN_KEY.concat(":").concat("1"); // 这里可以根据实际情况获取tenantId
// System.out.println("key = " + key);
// // 获取微信小程序配置信息
// JSONObject setting = settingService.getBySettingKey("mp-weixin");
// if (setting == null) {
// throw new RuntimeException("请先配置小程序");
// }
//
// // 从缓存获取access_token
// String value = redisUtil.get(key);
// System.out.println("redisTemplate-value = " + value);
// if (value != null) {
// JSONObject response = JSON.parseObject(value);
// String accessToken = response.getString("access_token");
// if (StrUtil.isNotBlank(accessToken)) {
// return accessToken;
// }
// }
//
// // 微信获取凭证接口
// String apiUrl = "https://api.weixin.qq.com/cgi-bin/token";
// // 组装url参数
// String url = apiUrl.concat("?grant_type=client_credential")
// .concat("&appid=").concat(setting.getString("appId"))
// .concat("&secret=").concat(setting.getString("appSecret"));
//
// // 执行get请求
// String result = cn.hutool.http.HttpUtil.get(url);
// System.out.println("获取access_token结果: " + result);
//
// // 解析access_token
// JSONObject response = JSON.parseObject(result);
// if (response.getString("access_token") != null) {
// // 存入缓存
// redisUtil.set(key, result, 7000L, TimeUnit.SECONDS);
// return response.getString("access_token");
// }
//
// throw new RuntimeException("小程序配置不正确");
// }
/**
* 文件上传位置(服务器)

1
src/main/java/com/gxwebsoft/shop/controller/ShopMainController.java

@ -42,6 +42,7 @@ public class ShopMainController extends BaseController {
try {
// 使用专门的商城信息获取方法
ShopVo shopVo = shopWebsiteService.getShopInfo(tenantId);
// log.debug("获取商城信息成功: {}", shopVo);
return success(shopVo);
} catch (IllegalArgumentException e) {
return fail(e.getMessage(), null);

5
src/main/java/com/gxwebsoft/shop/service/impl/ShopWebsiteServiceImpl.java

@ -2,9 +2,7 @@ package com.gxwebsoft.shop.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.gxwebsoft.cms.entity.CmsWebsite;
import com.gxwebsoft.cms.service.CmsWebsiteService;
import com.gxwebsoft.cms.service.impl.CmsWebsiteServiceImplHelper;
import com.gxwebsoft.common.core.utils.JSONUtil;
import com.gxwebsoft.common.core.utils.RedisUtil;
import com.gxwebsoft.shop.service.ShopWebsiteService;
@ -34,7 +32,7 @@ public class ShopWebsiteServiceImpl implements ShopWebsiteService {
/**
* 商城信息缓存键前缀
*/
private static final String SHOP_INFO_KEY_PREFIX = "shop_info:";
private static final String SHOP_INFO_KEY_PREFIX = "ShopInfo:";
@Override
public ShopVo getShopInfo(Integer tenantId) {
@ -57,7 +55,6 @@ public class ShopWebsiteServiceImpl implements ShopWebsiteService {
// 直接调用 CMS 服务获取站点信息,然后使用商城专用缓存
ShopVo shopVO = cmsWebsiteService.getSiteInfo(tenantId);
if (shopVO == null) {
throw new RuntimeException("请先创建商城");
}

18
src/main/java/com/gxwebsoft/shop/vo/MenuVo.java

@ -18,19 +18,22 @@ import java.util.List;
public class MenuVo implements Serializable {
@Schema(description = "导航ID")
private Integer navigationId;
private Integer id;
@Schema(description = "导航名称")
private String navigationName;
private String name;
@Schema(description = "导航链接")
private String navigationUrl;
@Schema(description = "导航类型")
private String type;
@Schema(description = "路由地址")
private String path;
@Schema(description = "导航图标")
private String navigationIcon;
private String icon;
@Schema(description = "导航颜色")
private String navigationColor;
private String color;
@Schema(description = "父级ID")
private Integer parentId;
@ -47,9 +50,6 @@ public class MenuVo implements Serializable {
@Schema(description = "打开方式 0当前窗口 1新窗口")
private Integer target;
@Schema(description = "导航类型")
private String navigationType;
@Schema(description = "子导航")
private List<MenuVo> children;
}

182
src/test/java/com/gxwebsoft/shop/OrderQueryTest.java

@ -1,182 +0,0 @@
package com.gxwebsoft.shop;
import com.gxwebsoft.shop.entity.ShopOrder;
import com.gxwebsoft.shop.service.ShopOrderService;
import com.gxwebsoft.shop.service.OrderCancelService;
import com.gxwebsoft.shop.config.OrderConfigProperties;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* 订单查询测试
*/
@Slf4j
@SpringBootTest
public class OrderQueryTest {
@Autowired
private ShopOrderService shopOrderService;
@Autowired
private OrderCancelService orderCancelService;
@Autowired
private OrderConfigProperties orderConfig;
@Test
public void testQuerySpecificOrder() {
String orderNo = "1957754623870595072";
log.info("查询订单号: {}", orderNo);
ShopOrder order = shopOrderService.getByOutTradeNo(orderNo);
if (order != null) {
log.info("订单信息:");
log.info(" 订单ID: {}", order.getOrderId());
log.info(" 订单号: {}", order.getOrderNo());
log.info(" 订单状态: {} (0=待支付, 1=待发货, 2=已取消, 3=已完成)", order.getOrderStatus());
log.info(" 支付状态: {} (false=未支付, true=已支付)", order.getPayStatus());
log.info(" 创建时间: {}", order.getCreateTime());
log.info(" 支付时间: {}", order.getPayTime());
log.info(" 取消时间: {}", order.getCancelTime());
log.info(" 租户ID: {}", order.getTenantId());
log.info(" 订单金额: {}", order.getTotalPrice());
log.info(" 取消原因: {}", order.getCancelReason());
// 检查是否符合自动取消条件
checkAutoCancelConditions(order);
// 计算什么时候会符合自动取消条件
calculateCancelTime(order);
} else {
log.warn("未找到订单号为 {} 的订单", orderNo);
}
}
private void checkAutoCancelConditions(ShopOrder order) {
log.info("\n=== 检查自动取消条件 ===");
// 1. 检查订单状态
boolean statusOk = (order.getOrderStatus() != null && order.getOrderStatus() == 0);
log.info("1. 订单状态检查: {} (需要为0-待支付)", statusOk ? "✓通过" : "✗不通过");
// 2. 检查支付状态
boolean payStatusOk = (order.getPayStatus() != null && !order.getPayStatus());
log.info("2. 支付状态检查: {} (需要为false-未支付)", payStatusOk ? "✓通过" : "✗不通过");
// 3. 检查创建时间是否超时
if (order.getCreateTime() != null) {
LocalDateTime createTime = order.getCreateTime();
LocalDateTime now = LocalDateTime.now();
// 获取超时配置
Integer timeoutMinutes = getTimeoutMinutes(order.getTenantId());
LocalDateTime expireTime = createTime.plusMinutes(timeoutMinutes);
boolean timeoutOk = now.isAfter(expireTime);
long minutesElapsed = java.time.Duration.between(createTime, now).toMinutes();
log.info("3. 超时检查:");
log.info(" 创建时间: {}", createTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
log.info(" 当前时间: {}", now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
log.info(" 超时配置: {}分钟", timeoutMinutes);
log.info(" 过期时间: {}", expireTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
log.info(" 已过时间: {}分钟", minutesElapsed);
log.info(" 是否超时: {} (需要超过{}分钟)", timeoutOk ? "✓是" : "✗否", timeoutMinutes);
// 4. 综合判断
boolean shouldCancel = statusOk && payStatusOk && timeoutOk;
log.info("\n=== 综合判断 ===");
log.info("是否符合自动取消条件: {}", shouldCancel ? "✓是" : "✗否");
if (shouldCancel) {
log.info("该订单符合自动取消任务的处理条件");
} else {
log.info("该订单不符合自动取消任务的处理条件");
if (!statusOk) log.info(" - 订单状态不是待支付状态");
if (!payStatusOk) log.info(" - 订单已支付");
if (!timeoutOk) log.info(" - 订单未超时");
}
} else {
log.warn("订单创建时间为空,无法判断是否超时");
}
}
private void calculateCancelTime(ShopOrder order) {
log.info("\n=== 计算自动取消时间点 ===");
if (order.getCreateTime() == null) {
log.warn("订单创建时间为空,无法计算取消时间");
return;
}
// 获取超时配置
Integer timeoutMinutes = getTimeoutMinutes(order.getTenantId());
LocalDateTime createTime = order.getCreateTime();
LocalDateTime cancelTime = createTime.plusMinutes(timeoutMinutes);
LocalDateTime now = LocalDateTime.now();
log.info("订单创建时间: {}", createTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
log.info("当前时间: {}", now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
log.info("超时配置: {}分钟", timeoutMinutes);
log.info("预计取消时间: {}", cancelTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
if (now.isBefore(cancelTime)) {
long minutesLeft = java.time.Duration.between(now, cancelTime).toMinutes();
long secondsLeft = java.time.Duration.between(now, cancelTime).getSeconds() % 60;
log.info("距离自动取消还有: {}分{}秒", minutesLeft, secondsLeft);
log.info("状态: ⏰ 等待中");
} else {
long minutesOverdue = java.time.Duration.between(cancelTime, now).toMinutes();
log.info("已超时: {}分钟", minutesOverdue);
log.info("状态: ⚠️ 应该被取消");
// 检查为什么没有被取消
if (order.getPayStatus() != null && order.getPayStatus()) {
log.info("原因: 订单已支付,不会被自动取消");
} else if (order.getOrderStatus() != null && order.getOrderStatus() != 0) {
log.info("原因: 订单状态不是待支付({}), 不会被自动取消", order.getOrderStatus());
} else {
log.info("原因: 订单符合取消条件,可能定时任务尚未执行或执行失败");
}
}
}
private Integer getTimeoutMinutes(Integer tenantId) {
// 检查是否有租户特殊配置
List<OrderConfigProperties.TenantCancelConfig> tenantConfigs = orderConfig.getAutoCancel().getTenantConfigs();
if (tenantConfigs != null) {
for (OrderConfigProperties.TenantCancelConfig config : tenantConfigs) {
if (config.isEnabled() && config.getTenantId().equals(tenantId)) {
return config.getTimeoutMinutes();
}
}
}
// 使用默认配置
return orderConfig.getAutoCancel().getDefaultTimeoutMinutes();
}
@Test
public void testFindExpiredOrders() {
log.info("=== 测试查找超时订单 ===");
Integer defaultTimeout = orderConfig.getAutoCancel().getDefaultTimeoutMinutes();
Integer batchSize = orderConfig.getAutoCancel().getBatchSize();
log.info("默认超时时间: {}分钟", defaultTimeout);
log.info("批量大小: {}", batchSize);
List<ShopOrder> expiredOrders = orderCancelService.findExpiredUnpaidOrders(defaultTimeout, batchSize);
log.info("找到{}个超时订单", expiredOrders.size());
for (ShopOrder order : expiredOrders) {
log.info("超时订单: {} - 创建时间: {}", order.getOrderNo(), order.getCreateTime());
}
}
}
Loading…
Cancel
Save