# jsm-lark-comment-sync-lambda

独立 Lambda，双向同步飞书 alert 卡片消息与 JSM issue 评论：

- **飞书 → JSM**：用户在 alert 卡片 thread 中的回复 → JSM issue comment
- **JSM → 飞书**：JSM issue 新评论 → 回复到对应飞书 alert 卡片 thread

不依赖其他 Lambda 的 `ticket_thread` 表，独立维护自己的映射。

## 数据表（独立）

```sql
-- alert 卡片消息 ID → JSM issue key 映射
CREATE TABLE lark_card_issue_mapping (
  card_message_id VARCHAR(128) NOT NULL,  -- 飞书卡片消息 ID（message_id）
  issue_key      VARCHAR(64)  NOT NULL,   -- JSM issue key（如 O11Y-17072）
  chat_id        VARCHAR(128) NOT NULL,
  PRIMARY KEY (card_message_id),
  KEY idx_issue_key (issue_key),
  KEY idx_chat_id (chat_id)
);

-- 评论同步幂等日志
CREATE TABLE comment_sync_log (
  id                 BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  source             VARCHAR(16) NOT NULL,         -- 'lark' 或 'jsm'
  source_comment_id  VARCHAR(128) NOT NULL,
  issue_key          VARCHAR(64)  DEFAULT NULL,
  chat_id            VARCHAR(128) DEFAULT NULL,
  root_message_id    VARCHAR(128) DEFAULT NULL,
  target_comment_id  VARCHAR(128) DEFAULT NULL,
  content_hash       VARCHAR(64)  DEFAULT NULL,
  status             VARCHAR(16) NOT NULL DEFAULT 'processing',
  retry_count        INT NOT NULL DEFAULT 0,
  last_error         VARCHAR(1024) DEFAULT NULL,
  created_at         TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at         TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_source_comment (source, source_comment_id),
  KEY idx_source_status (source, status),
  KEY idx_issue_key (issue_key)
);
```

## 环境变量

| 变量 | 必须 | 说明 |
|------|------|------|
| `DATABASE_URL` | ✅ | MySQL 连接串 |
| `JSM_SITE` | ✅ | 如 `https://xxx.atlassian.net` |
| `JIRA_EMAIL` | ✅ | JSM 账号邮箱 |
| `JIRA_API_TOKEN` | ✅ | JSM API Token |
| `LARK_BOT_APP_ID` | ✅ | 飞书自建应用 App ID |
| `LARK_BOT_APP_SECRET` | ✅ | 飞书自建应用 App Secret |
| `JSM_SYNC_BOT_ACCOUNT_ID` | ❌ | JSM 同步 bot 账号 ID，用于过滤该 bot 发出的评论 |
| `DB_CONNECTION_LIMIT` | ❌ | 默认 `5` |

## 事件来源

- **飞书事件订阅**：`im.message.receive_v1` → API Gateway → 此 Lambda
- **JSM Automation/Webhook**：comment 事件 → POST 到同一 Lambda URL

## 同步逻辑

### 飞书 → JSM（用户回复卡片 thread）

```
收到 im.message.receive_v1
  ├── sender_type=app, msg_type=interactive
  │   → 解析卡片 JSON 中的 Jira URL，提取 issue key
  │   → 写入 lark_card_issue_mapping
  │
  └── sender_type=user, 有 root_id
      → 根据 root_id（卡片消息 ID）查 lark_card_issue_mapping
      → 拿到 issue_key，创建 JSM comment
```

### JSM → 飞书

```
收到 JSM comment webhook（body.eventType=comment, body.issueKey, body.commentId）
  → 根据 issue_key 查 lark_card_issue_mapping
  → 拿到 card_message_id，回复到飞书卡片 thread
```

## 防循环与防重复

- **防循环**：JSM 侧过滤 `JSM_SYNC_BOT_ACCOUNT_ID`（如果配置了）；飞书侧只处理 `sender_type=user` 的回复
- **防重复**：`comment_sync_log(source, source_comment_id)` 唯一键 + `INSERT IGNORE` 原子抢占 + `processing/success/failed` 状态机

## 富文本支持

| 方向 | 纯文本 | 加粗/斜体/代码块 | @提及 | 图片/附件 |
|------|--------|---------------|-------|---------|
| JSM → 飞书 | ✅ | ✅ | ✅ | ✅ |
| 飞书 → JSM | ✅ | ✅ | ✅ | ✅ |

## 本地检查

```bash
npm install && npm run check
```
