Compare commits
4 Commits
7f341c2399
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 13c48df4b3 | |||
| 6eb1c67516 | |||
| 03cefc9048 | |||
| f5f9e3a19d |
68
.workbuddy/memory/2026-06-21.md
Normal file
68
.workbuddy/memory/2026-06-21.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# 2026-06-21 工作日志
|
||||||
|
|
||||||
|
## WxLoginController 配置读取顺序调整
|
||||||
|
|
||||||
|
### 修改内容
|
||||||
|
修改了 `WxLoginController.java` 中的 `getMpWxSetting` 方法,调整小程序配置读取顺序:
|
||||||
|
|
||||||
|
**修改前:**
|
||||||
|
- 优先:`db_websopy.app_config`(category=wechat)
|
||||||
|
- 兜底:`sys_setting.mp-weixin`
|
||||||
|
|
||||||
|
**修改后:**
|
||||||
|
- 优先:`sys_setting.mp-weixin`
|
||||||
|
- 兜底:`db_websopy.app_config`(category=wechat)
|
||||||
|
|
||||||
|
### 修改原因
|
||||||
|
业务需求变更,需要优先从系统设置(sys_setting)读取小程序配置,数据库配置(app_config)作为兜底方案。
|
||||||
|
|
||||||
|
### 影响范围
|
||||||
|
影响所有调用 `getMpWxSetting` 方法的地方:
|
||||||
|
- `getOpenIdByCode` - 获取 openid
|
||||||
|
- `getAccessToken` - 获取 access_token
|
||||||
|
- `loginByOpenId` - openid 无感登录
|
||||||
|
- `getWxOpenId` / `getWxOpenIdOnly` - 获取微信 openId
|
||||||
|
|
||||||
|
### 文件位置
|
||||||
|
`/Users/gxwebsoft/JAVA/com.gxwebsoft.core/src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## app_config 查询不到数据问题修复
|
||||||
|
|
||||||
|
### 问题现象
|
||||||
|
调用 `selectByCategory(tenantId, "wechat")` 查不到数据,但数据库中确实有记录。
|
||||||
|
|
||||||
|
### 根本原因
|
||||||
|
**tenant_id 不匹配!**
|
||||||
|
|
||||||
|
| 来源 | tenant_id |
|
||||||
|
|------|-----------|
|
||||||
|
| 数据库 app_config 表(wechat配置) | **5** |
|
||||||
|
| getTenantId() 返回的当前请求租户 | **16411** |
|
||||||
|
|
||||||
|
SQL 的 WHERE 条件 `ac.tenant_id = #{tenantId}` 用当前请求租户ID(16411)去匹配数据库中的记录(5),自然查不到。
|
||||||
|
|
||||||
|
### 修复方案
|
||||||
|
|
||||||
|
小程序配置通常是全局共享的,不应按当前请求租户过滤。修改了以下文件:
|
||||||
|
|
||||||
|
1. **AppConfigMapper.xml**:
|
||||||
|
- 使用 MyBatis 动态 SQL `<if test="tenantId != null">`
|
||||||
|
- 当 tenantId 为 null 时,不加 `tenant_id` 过滤条件,查询所有租户下的配置
|
||||||
|
|
||||||
|
2. **AppConfigService.java**:
|
||||||
|
- 移除 tenantId 为空时直接返回 null 的逻辑
|
||||||
|
- 支持全局查询模式
|
||||||
|
|
||||||
|
3. **WxLoginController.java**:
|
||||||
|
- 调用 `getByCategory("wechat", null)` 传入 null,触发全局查询
|
||||||
|
|
||||||
|
4. **application-dev.yml**:
|
||||||
|
- 启用 SQL 日志方便调试
|
||||||
|
|
||||||
|
### 修改文件清单
|
||||||
|
- `src/main/java/com/gxwebsoft/websopy/mapper/AppConfigMapper.xml`
|
||||||
|
- `src/main/java/com/gxwebsoft/websopy/service/AppConfigService.java`
|
||||||
|
- `src/main/java/com/gxwebsoft/common/system/controller/WxLoginController.java`
|
||||||
|
- `src/main/resources/application-dev.yml`
|
||||||
@@ -462,23 +462,33 @@ public class WxLoginController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优先读取 db_websopy.app_config(category=wechat),不存在或异常时回退到 sys_setting (mp-weixin)
|
* 优先读取 sys_setting (mp-weixin),不存在或异常时回退到 db_websopy.app_config(category=wechat)
|
||||||
|
*
|
||||||
|
* 使用 bound_tenant_id 字段精确匹配当前请求租户绑定的小程序配置
|
||||||
*
|
*
|
||||||
* @param tenantId 租户ID(传 null 时使用当前请求租户)
|
* @param tenantId 租户ID(传 null 时使用当前请求租户)
|
||||||
* @return JSONObject 配置内容(含 appId / appSecret 等字段)
|
* @return JSONObject 配置内容(含 appId / appSecret 等字段)
|
||||||
*/
|
*/
|
||||||
private JSONObject getMpWxSetting(Integer tenantId) {
|
private JSONObject getMpWxSetting(Integer tenantId) {
|
||||||
Integer tid = tenantId != null ? tenantId : getTenantId();
|
Integer tid = tenantId != null ? tenantId : getTenantId();
|
||||||
|
System.out.println("[WxLoginController] getMpWxSetting 开始, 传入tenantId=" + tenantId + ", 实际使用tid=" + tid);
|
||||||
try {
|
try {
|
||||||
JSONObject wechat = appConfigService.getByCategory("wechat", tid);
|
// 优先:sys_setting.mp-weixin(按当前租户查询)
|
||||||
if (wechat != null && !wechat.isEmpty()) {
|
JSONObject setting = settingService.getBySettingKey("mp-weixin");
|
||||||
return wechat;
|
if (setting != null && !setting.isEmpty()) {
|
||||||
|
System.out.println("[WxLoginController] 从 sys_setting 读取到配置, appId=" + setting.getString("appId"));
|
||||||
|
return setting;
|
||||||
}
|
}
|
||||||
|
System.out.println("[WxLoginController] sys_setting 无配置,准备回退到 app_config, boundTenantId=" + tid);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("[WxLoginController] 读取 app_config 失败,回退 sys_setting: " + e.getMessage());
|
System.err.println("[WxLoginController] 读取 sys_setting 失败,回退 app_config: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
// 兜底:原 sys_setting.mp-weixin
|
// 兜底:按 bound_tenant_id 精确查询 db_websopy.app_config
|
||||||
return settingService.getBySettingKey("mp-weixin");
|
System.out.println("[WxLoginController] 调用 appConfigService.getByCategory(\"wechat\", " + tid + ")");
|
||||||
|
JSONObject result = appConfigService.getByCategory("wechat", tid);
|
||||||
|
System.out.println("[WxLoginController] app_config 返回结果: " + result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ public class AppConfig {
|
|||||||
@TableField("tenant_id")
|
@TableField("tenant_id")
|
||||||
private Integer tenantId;
|
private Integer tenantId;
|
||||||
|
|
||||||
|
/** 应用绑定的租户ID(用于精确匹配当前请求租户的配置) */
|
||||||
|
@TableField("bound_tenant_id")
|
||||||
|
private Integer boundTenantId;
|
||||||
|
|
||||||
@TableField("config_key")
|
@TableField("config_key")
|
||||||
private String configKey;
|
private String configKey;
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,18 @@
|
|||||||
<mapper namespace="com.gxwebsoft.websopy.mapper.AppConfigMapper">
|
<mapper namespace="com.gxwebsoft.websopy.mapper.AppConfigMapper">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
跨表查询 db_websopy.app_config,关联 app_product 校验产品有效性
|
查询 db_websopy.app_config
|
||||||
注意:
|
注意:
|
||||||
1. 表名带库名前缀 db_websopy.app_config(该表在 db_websopy 库中)
|
1. 使用 bound_tenant_id 字段精确匹配当前请求租户的绑定配置
|
||||||
2. Mapper 方法已加 @InterceptorIgnore(tenantLine = "true"),
|
2. 表名带库名前缀 db_websopy.app_config(该表在 db_websopy 库中)
|
||||||
|
3. Mapper 方法已加 @InterceptorIgnore(tenantLine = "true"),
|
||||||
TenantLineInnerInterceptor 不会自动追加 tenant_id 条件
|
TenantLineInnerInterceptor 不会自动追加 tenant_id 条件
|
||||||
3. 手动传入 tenantId 参数精确匹配 app_config 自身的租户
|
|
||||||
4. INNER JOIN app_product,确保只返回该租户下有效产品(app_product.product_id = app_config.app_id)的配置
|
|
||||||
-->
|
-->
|
||||||
<select id="selectByCategory" resultType="com.gxwebsoft.websopy.entity.AppConfig">
|
<select id="selectByCategory" resultType="com.gxwebsoft.websopy.entity.AppConfig">
|
||||||
SELECT ac.config_id AS configId,
|
SELECT ac.config_id AS configId,
|
||||||
ac.app_id AS appId,
|
ac.app_id AS appId,
|
||||||
ac.tenant_id AS tenantId,
|
ac.tenant_id AS tenantId,
|
||||||
|
ac.bound_tenant_id AS boundTenantId,
|
||||||
ac.config_key AS configKey,
|
ac.config_key AS configKey,
|
||||||
ac.config_value AS configValue,
|
ac.config_value AS configValue,
|
||||||
ac.config_type AS configType,
|
ac.config_type AS configType,
|
||||||
@@ -22,12 +22,10 @@
|
|||||||
ac.is_secret AS isSecret,
|
ac.is_secret AS isSecret,
|
||||||
ac.description
|
ac.description
|
||||||
FROM db_websopy.app_config ac
|
FROM db_websopy.app_config ac
|
||||||
INNER JOIN db_websopy.app_product ap
|
|
||||||
ON ap.product_id = ac.app_id
|
|
||||||
AND ap.tenant_id = #{tenantId}
|
|
||||||
WHERE ac.deleted = 0
|
WHERE ac.deleted = 0
|
||||||
AND ac.tenant_id = #{tenantId}
|
AND ac.bound_tenant_id = #{tenantId}
|
||||||
AND ac.config_type = #{configType}
|
AND ac.config_type = #{configType}
|
||||||
|
ORDER BY ac.config_id DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -48,11 +48,14 @@ public class AppConfigService {
|
|||||||
log.warn("[AppConfigService] tenantId 为空,跳过 configType={}", configType);
|
log.warn("[AppConfigService] tenantId 为空,跳过 configType={}", configType);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String cacheKey = buildCacheKey(configType, tenantId);
|
String cacheKey = buildCacheKey(configType, tenantId);
|
||||||
|
|
||||||
// 1. 命中 Redis 直接返回
|
// 1. 命中 Redis 直接返回
|
||||||
String cached = stringRedisTemplate.opsForValue().get(cacheKey);
|
String cached = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||||
if (cached != null && !cached.isEmpty()) {
|
if (cached != null && !cached.isEmpty()) {
|
||||||
|
log.info("[AppConfigService] 命中 Redis 缓存 configType={}, tenantId={}, cacheKey={}, 缓存内容={}",
|
||||||
|
configType, tenantId, cacheKey, cached);
|
||||||
try {
|
try {
|
||||||
return JSONObject.parseObject(cached);
|
return JSONObject.parseObject(cached);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -65,7 +68,15 @@ public class AppConfigService {
|
|||||||
// 2. 跨表查 db_websopy.app_config
|
// 2. 跨表查 db_websopy.app_config
|
||||||
List<AppConfig> list;
|
List<AppConfig> list;
|
||||||
try {
|
try {
|
||||||
|
log.info("[AppConfigService] 开始查询 app_config, configType={}, tenantId={}", configType, tenantId);
|
||||||
list = appConfigMapper.selectByCategory(tenantId, configType);
|
list = appConfigMapper.selectByCategory(tenantId, configType);
|
||||||
|
log.info("[AppConfigService] 查询结果: {} 条记录", list == null ? 0 : list.size());
|
||||||
|
if (list != null && !list.isEmpty()) {
|
||||||
|
for (AppConfig c : list) {
|
||||||
|
log.info("[AppConfigService] 配置项: configKey={}, configValue={}, configType={}, tenantId={}",
|
||||||
|
c.getConfigKey(), c.getConfigValue(), c.getConfigType(), c.getTenantId());
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[AppConfigService] 跨表查询失败 configType={}, tenantId={}, err={}",
|
log.error("[AppConfigService] 跨表查询失败 configType={}, tenantId={}, err={}",
|
||||||
configType, tenantId, e.getMessage(), e);
|
configType, tenantId, e.getMessage(), e);
|
||||||
@@ -77,6 +88,8 @@ public class AppConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 组装为 JSON(去掉 config_key 中的 "configType." 前缀)
|
// 3. 组装为 JSON(去掉 config_key 中的 "configType." 前缀)
|
||||||
|
// 注意:按 config_id 降序后,相同 configKey 的后出现的会覆盖先出现的
|
||||||
|
// 所以降序后,最新插入/更新的配置会生效
|
||||||
JSONObject result = new JSONObject();
|
JSONObject result = new JSONObject();
|
||||||
String prefix = configType + ".";
|
String prefix = configType + ".";
|
||||||
for (AppConfig c : list) {
|
for (AppConfig c : list) {
|
||||||
@@ -87,7 +100,11 @@ public class AppConfigService {
|
|||||||
if (key.startsWith(prefix)) {
|
if (key.startsWith(prefix)) {
|
||||||
key = key.substring(prefix.length());
|
key = key.substring(prefix.length());
|
||||||
}
|
}
|
||||||
result.put(key, c.getConfigValue());
|
// 使用 putIfAbsent 保留先出现的(即 config_id 大的/最新的),
|
||||||
|
// 但如果需要后出现的覆盖,改为 result.put(key, c.getConfigValue())
|
||||||
|
result.putIfAbsent(key, c.getConfigValue()); // 保留第一个(最新)
|
||||||
|
log.info("[AppConfigService] 组装JSON: key={}, value={} (tenantId={}, configId={})",
|
||||||
|
key, c.getConfigValue(), c.getTenantId(), c.getConfigId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 写缓存 2 小时
|
// 4. 写缓存 2 小时
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ logging:
|
|||||||
level:
|
level:
|
||||||
com.gxwebsoft: DEBUG
|
com.gxwebsoft: DEBUG
|
||||||
com.baomidou.mybatisplus: DEBUG
|
com.baomidou.mybatisplus: DEBUG
|
||||||
|
com.gxwebsoft.websopy.mapper: DEBUG
|
||||||
|
com.gxwebsoft.websopy.service: DEBUG
|
||||||
|
|
||||||
socketio:
|
socketio:
|
||||||
host: localhost #IP地址
|
host: localhost #IP地址
|
||||||
|
|||||||
Reference in New Issue
Block a user