Просмотр исходного кода

fix(backend): 迁移174 product_skus添加min_stock_alert列+同步schema.sql(SKU库存预警)

Xiaogang Liao 1 месяц назад
Родитель
Сommit
a2c606c244

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -7827,5 +7827,8 @@ private void runMigration100() {
 					") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存入库明细表'");
 			log.info("已创建inventory_inbound_items表");
 		} catch (Exception e) { /* 表已存在,忽略 */ }
+
+		// 迁移174: product_skus 添加 min_stock_alert 列(SKU级别库存预警阈值,配合库存管理模块)
+		ensureColumn("product_skus", "min_stock_alert", "INT DEFAULT 0 COMMENT '库存预警阈值(低于此值预警)'");
 	}
 }

+ 51 - 5
cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java

@@ -260,6 +260,9 @@ public class MembershipService implements MembershipServiceInterface {
      * 创建订单
      */
     public PaymentOrderDTO createOrder(Long userId, Long familyId, String levelCode, String paymentType, String period, Long userCouponId) {
+        if (familyId == null) {
+            throw new RuntimeException("请先创建或加入家庭后再购买会员");
+        }
         MembershipLevel level = levelMapper.selectOne(
                 new LambdaQueryWrapper<MembershipLevel>()
                         .eq(MembershipLevel::getLevelCode, levelCode)
@@ -425,7 +428,16 @@ public class MembershipService implements MembershipServiceInterface {
                 return true;
             }
             if ("paid".equals(order.getStatus())) {
-                return true;
+                // 幂等增强:已 paid 但无开通记录(历史 families.creator_id 缺失导致开通失败)→ 补开通
+                Long membershipCount = membershipMapper.selectCount(
+                        new LambdaQueryWrapper<FamilyMembership>()
+                                .eq(FamilyMembership::getOrderNo, orderNo)
+                                .eq(FamilyMembership::getPaymentStatus, "paid"));
+                if (membershipCount != null && membershipCount > 0) {
+                    return true;
+                }
+                log.info("订单已paid但无开通记录, 触发补开通: orderNo={}", orderNo);
+                // 不短路,继续走下方开通逻辑
             }
 
             // 标记该期已付
@@ -490,12 +502,13 @@ public class MembershipService implements MembershipServiceInterface {
                 couponService.markUsed(order.getUserCouponId(), order.getId());
             }
 
-            // 查找该家庭的创建者(家庭管理员)
-            Family family = familyMapper.selectById(order.getFamilyId());
-            if (family == null || family.getCreatorId() == null) {
+            // 查找该家庭的创建者(家庭管理员),creator_id 缺失时回退 users.family_id
+            Long adminUserId = resolveFamilyAdminUserId(order.getFamilyId());
+            if (adminUserId == null) {
+                log.warn("会员开通失败: 家庭无关联用户, familyId={}, orderNo={}",
+                        order.getFamilyId(), order.getOrderNo());
                 return false;
             }
-            Long adminUserId = family.getCreatorId();
             User adminUser = userMapper.selectById(adminUserId);
             if (adminUser == null) {
                 return false;
@@ -558,6 +571,39 @@ public class MembershipService implements MembershipServiceInterface {
         }
     }
 
+    /**
+     * 解析家庭管理员用户ID。
+     * 优先 families.creator_id;缺失时回退 users.family_id
+     * (优先 is_family_admin=true 的用户,否则取任一 openid 非空用户)。
+     * 用于会员开通/虚拟支付兜底任务(早期 families.creator_id 从未写入,需回退)。
+     */
+    public Long resolveFamilyAdminUserId(Long familyId) {
+        if (familyId == null) {
+            return null;
+        }
+        Family family = familyMapper.selectById(familyId);
+        if (family != null && family.getCreatorId() != null) {
+            return family.getCreatorId();
+        }
+        // 回退:从 users.family_id 反查家庭成员
+        List<User> members = userMapper.selectList(
+                new LambdaQueryWrapper<User>()
+                        .eq(User::getFamilyId, familyId)
+                        .isNotNull(User::getOpenid)
+                        .ne(User::getOpenid, "")
+        );
+        if (members == null || members.isEmpty()) {
+            return null;
+        }
+        // 优先家庭管理员,其次任一成员
+        for (User u : members) {
+            if (Boolean.TRUE.equals(u.getIsFamilyAdmin())) {
+                return u.getId();
+            }
+        }
+        return members.get(0).getId();
+    }
+
     // ==================== 会员升级与过期 ====================
 
     /**

+ 9 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/UnifiedKnowledgeBaseService.java

@@ -222,7 +222,15 @@ public class UnifiedKnowledgeBaseService {
             }
         }
 
-        load();
+        // 直接内存填充,不调 load() 以避免 load()→importFromClasspath()→load() 的无限递归
+        clearIndexes();
+        for (String key : MODULE_KEYS) {
+            Object content = root.get(key);
+            if (content instanceof Map) {
+                sectionMap.put(key, (Map<String, Object>) content);
+            }
+        }
+        buildIndexes();
         return "统一知识库导入完成: " + imported + "/" + MODULE_KEYS.size() + " 模块, 版本=" + version;
     }
 

+ 11 - 3
cfc-backend/src/main/java/com/etotem/cfc/task/VirtualPayTimeoutTask.java

@@ -153,6 +153,14 @@ public class VirtualPayTimeoutTask {
 
     /** ORD 前缀(会员订单)单笔处理,返回是否处理(解锁/关单) */
     private boolean handlePaymentOrder(PaymentOrder order) {
+        // family_id=NULL 的订单(下单接口未校验历史脏数据):无法向微信查单,直接置 cancelled
+        if (order.getFamilyId() == null) {
+            order.setStatus("cancelled");
+            order.setUpdatedAt(new Date());
+            paymentOrderMapper.updateById(order);
+            log.warn("虚拟支付超时兜底: 会员订单无family_id, 置cancelled: orderNo={}", order.getOrderNo());
+            return true;
+        }
         String openid = resolveFamilyCreatorOpenid(order.getFamilyId());
         if (openid == null) {
             log.warn("虚拟支付超时兜底: 会员订单openid无法解析, 跳过不关单: orderNo={}", order.getOrderNo());
@@ -219,11 +227,11 @@ public class VirtualPayTimeoutTask {
      * 家庭或创建者缺失 → 返回 null(调用方不得关单)。
      */
     private String resolveFamilyCreatorOpenid(Long familyId) {
-        Family family = familyMapper.selectById(familyId);
-        if (family == null || family.getCreatorId() == null) {
+        Long adminUserId = membershipService.resolveFamilyAdminUserId(familyId);
+        if (adminUserId == null) {
             return null;
         }
-        User buyer = userMapper.selectById(family.getCreatorId());
+        User buyer = userMapper.selectById(adminUserId);
         if (buyer == null || buyer.getOpenid() == null || buyer.getOpenid().isEmpty()) {
             return null;
         }

+ 1 - 0
cfc-backend/src/main/resources/schema.sql

@@ -1165,6 +1165,7 @@ CREATE TABLE IF NOT EXISTS product_skus (
     specs JSON COMMENT '规格选项JSON: [{"name":"颜色","value":"红色"},{"name":"尺码","value":"M"}]',
     price INT DEFAULT 0 COMMENT 'SKU价格(分)',
     stock INT DEFAULT 0 COMMENT '库存数量',
+    min_stock_alert INT DEFAULT 0 COMMENT '库存预警阈值(低于此值预警)',
     min_quantity INT DEFAULT 1 COMMENT '最小购买数量限制',
     original_price INT DEFAULT 0 COMMENT '原价(分)',
     image VARCHAR(500) COMMENT 'SKU图片',

+ 1 - 1
cfc-langgraph/docker-compose.yml

@@ -33,7 +33,7 @@ services:
       - JAVA_CONTEXT_URL=http://172.17.0.1:9082/api/ai/context
       - CHROMA_DB_PATH=/data/chroma_db
       - JSON_LOGS=true
-      - LANGCHAIN_TRACING_V2=${LANGCHAIN_TRACING_V2:-false}
+      # LangSmith 三个变量从 env_file (.env.production) 读取,不再覆盖
     volumes:
       - langgraph-data:/data
     networks:

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-ba6766ef89bdf49b6f50dc1598e59985697ebfa3
+26a41f9a5a886009e8b6900acd46620f0f968258