## 功能概述 - 用户首次使用需验证邀请码才能发起对话 - 在输入框聚焦和点击示例时触发验证检查 - 使用弹窗形式展示邀请码输入界面,包含企业端用户提示和微信二维码 ## 主要变更 ### 新增文件 - `services/invitationService.ts`: 邀请码验证服务,处理验证逻辑和状态管理 - `views/invitationModal.ts`: 邀请码验证弹窗组件(HTML/CSS/JS) - `docs/invitation-code-design.md`: 邀请码功能设计文档 ### 修改文件 - `extension.ts`: 添加更换邀请码命令,退出登录时清除验证状态 - `panels/ICHelperPanel.ts`: 添加邀请码验证状态检查和验证消息处理 - `services/apiClient.ts`: 添加邀请码验证接口调用 - `types/api.ts`: 添加邀请码相关类型定义 - `views/inputArea.ts`: 输入框聚焦时触发邀请码验证检查 - `views/exampleShowcase.ts`: 点击示例时先检查邀请码验证状态 - `views/webviewContent.ts`: 集成邀请码弹窗到主界面 ## 技术实现 - 验证状态保存在 ExtensionContext.globalState 中 - 使用后端接口 POST /api/invitation/verify 进行验证 - 弹窗样式适配 VS Code 主题 - 支持回车键提交验证
19 KiB
邀请码验证功能设计方案
一、整体流程
用户首次使用 → 检查邀请码状态 → 未验证则弹窗输入 → 后端验证 → 验证通过后可正常对话
二、前端设计
2.1 邀请码状态管理
在 ExtensionContext.globalState 中存储邀请码验证状态:
// 存储结构
{
"invitationCodeVerified": true,
"invitationCode": "INVITE2024ABC",
"verifiedTime": "2024-01-20T10:30:00"
}
2.2 UI 交互流程
弹窗输入邀请码
使用 vscode.window.showInputBox 实现:
const invitationCode = await vscode.window.showInputBox({
prompt: '请输入邀请码以继续使用 IC Coder',
placeHolder: '例如:INVITE2024ABC',
ignoreFocusOut: true,
validateInput: (value) => {
if (!value || value.trim().length === 0) {
return '邀请码不能为空';
}
if (value.length < 6) {
return '邀请码格式不正确';
}
return null;
}
});
验证结果提示
- 成功:
vscode.window.showInformationMessage('邀请码验证成功!') - 失败:
vscode.window.showErrorMessage('邀请码无效或已过期,请重新输入')
2.3 验证时机
在以下场景触发邀请码验证:
- 用户首次发送消息时(在
handleUserMessage中检查) - 用户登录后(在登录成功回调中检查)
- Token 过期重新登录后
2.4 前端验证流程图
发送消息前检查
├─ 检查是否已登录
│ └─ 未登录 → 提示登录
├─ 检查邀请码是否已验证
│ ├─ 未验证
│ │ ├─ 弹窗输入邀请码
│ │ ├─ 调用后端验证接口 POST /api/invitation/verify
│ │ ├─ 验证成功
│ │ │ ├─ 保存验证状态到 globalState
│ │ │ └─ 继续发送消息
│ │ └─ 验证失败
│ │ ├─ 显示错误提示
│ │ └─ 阻止发送消息
│ └─ 已验证 → 继续发送消息
2.5 前端文件修改清单
新增文件
src/services/invitationService.ts - 邀请码服务
/**
* 邀请码验证服务
*/
export class InvitationService {
/**
* 检查用户是否已验证邀请码
*/
static async isVerified(context: vscode.ExtensionContext): Promise<boolean>
/**
* 验证邀请码
*/
static async verifyCode(code: string): Promise<boolean>
/**
* 保存验证状态
*/
static async saveVerificationStatus(
context: vscode.ExtensionContext,
code: string
): Promise<void>
/**
* 清除验证状态(用于退出登录)
*/
static async clearVerificationStatus(
context: vscode.ExtensionContext
): Promise<void>
/**
* 显示邀请码输入弹窗
*/
static async showInputDialog(): Promise<string | undefined>
}
修改文件
src/utils/messageHandler.ts
- 在
handleUserMessage函数开头添加邀请码验证检查
src/services/apiClient.ts
- 添加
verifyInvitationCode函数 - 添加
checkInvitationStatus函数
src/types/api.ts
- 添加邀请码相关类型定义
src/panels/ICHelperPanel.ts
- 在面板创建时检查邀请码状态(可选)
src/extension.ts
- 在登录成功后检查邀请码状态
2.6 类型定义
在 src/types/api.ts 中添加:
// ============== 邀请码验证 ==============
/**
* 邀请码验证请求
* POST /api/invitation/verify
*/
export interface InvitationVerifyRequest {
/** 邀请码 */
code: string;
}
/**
* 邀请码验证响应
*/
export interface InvitationVerifyResponse {
/** 响应代码 */
code: number;
/** 响应消息 */
msg: string;
/** 验证结果数据 */
data?: {
/** 是否验证成功 */
verified: boolean;
};
}
/**
* 邀请码状态响应
* GET /api/invitation/status
*/
export interface InvitationStatusResponse {
/** 响应代码 */
code: number;
/** 响应消息 */
msg?: string;
/** 状态数据 */
data?: {
/** 是否已验证 */
verified: boolean;
/** 使用的邀请码 */
invitationCode?: string;
/** 验证时间 */
verifiedTime?: string;
};
}
三、后端设计
3.1 数据库设计
邀请码表 (invitation_codes)
CREATE TABLE invitation_codes (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
code VARCHAR(32) UNIQUE NOT NULL COMMENT '邀请码',
max_uses INT DEFAULT 1 COMMENT '最大使用次数,-1表示无限制',
used_count INT DEFAULT 0 COMMENT '已使用次数',
expire_time DATETIME COMMENT '过期时间,NULL表示永不过期',
created_by BIGINT COMMENT '创建者用户ID',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
status TINYINT DEFAULT 1 COMMENT '状态:1-有效,0-禁用',
remark VARCHAR(500) COMMENT '备注',
INDEX idx_code (code),
INDEX idx_status (status),
INDEX idx_expire_time (expire_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请码表';
用户邀请码关联表 (user_invitation)
CREATE TABLE user_invitation (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
invitation_code VARCHAR(32) NOT NULL COMMENT '使用的邀请码',
verified_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '验证时间',
ip_address VARCHAR(50) COMMENT '验证时的IP地址',
UNIQUE KEY uk_user_id (user_id),
INDEX idx_invitation_code (invitation_code),
INDEX idx_verified_time (verified_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户邀请码关联表';
3.2 API 接口设计
3.2.1 验证邀请码
接口地址:POST /api/invitation/verify
请求头:
Authorization: Bearer {token}
Content-Type: application/json
请求体:
{
"code": "INVITE2024ABC"
}
响应示例:
成功:
{
"code": 200,
"msg": "验证成功",
"data": {
"verified": true
}
}
失败:
{
"code": 400,
"msg": "邀请码无效或已过期"
}
{
"code": 400,
"msg": "邀请码使用次数已达上限"
}
{
"code": 400,
"msg": "您已验证过邀请码,无需重复验证"
}
3.2.2 查询验证状态
接口地址:GET /api/invitation/status
请求头:
Authorization: Bearer {token}
响应示例:
已验证:
{
"code": 200,
"msg": "success",
"data": {
"verified": true,
"invitationCode": "INVITE2024ABC",
"verifiedTime": "2024-01-20T10:30:00"
}
}
未验证:
{
"code": 200,
"msg": "success",
"data": {
"verified": false
}
}
3.2.3 管理接口(管理员使用)
生成邀请码:POST /api/admin/invitation/generate
请求体:
{
"count": 10,
"maxUses": 1,
"expireTime": "2024-12-31T23:59:59",
"remark": "2024年1月批次"
}
响应:
{
"code": 200,
"msg": "生成成功",
"data": {
"codes": [
"INVITE2024001",
"INVITE2024002",
"..."
]
}
}
查询邀请码列表:GET /api/admin/invitation/list
禁用邀请码:PUT /api/admin/invitation/disable/{code}
查询使用记录:GET /api/admin/invitation/usage/{code}
3.3 后端验证逻辑
验证流程
public boolean verifyInvitationCode(Long userId, String code) {
// 1. 检查邀请码是否存在
InvitationCode invitationCode = invitationCodeMapper.selectByCode(code);
if (invitationCode == null) {
throw new BusinessException("邀请码不存在");
}
// 2. 检查邀请码状态
if (invitationCode.getStatus() != 1) {
throw new BusinessException("邀请码已被禁用");
}
// 3. 检查是否过期
if (invitationCode.getExpireTime() != null
&& invitationCode.getExpireTime().before(new Date())) {
throw new BusinessException("邀请码已过期");
}
// 4. 检查使用次数
if (invitationCode.getMaxUses() != -1
&& invitationCode.getUsedCount() >= invitationCode.getMaxUses()) {
throw new BusinessException("邀请码使用次数已达上限");
}
// 5. 检查用户是否已验证过
UserInvitation existing = userInvitationMapper.selectByUserId(userId);
if (existing != null) {
throw new BusinessException("您已验证过邀请码,无需重复验证");
}
// 6. 创建用户验证记录
UserInvitation userInvitation = new UserInvitation();
userInvitation.setUserId(userId);
userInvitation.setInvitationCode(code);
userInvitation.setVerifiedTime(new Date());
userInvitationMapper.insert(userInvitation);
// 7. 增加邀请码使用次数
invitationCodeMapper.incrementUsedCount(code);
return true;
}
3.4 权限拦截
在对话接口中添加邀请码验证拦截:
@PostMapping("/dialog/stream")
public SseEmitter dialog(@RequestBody DialogRequest request) {
// 获取当前用户ID
Long userId = SecurityUtils.getUserId();
// 检查用户是否已验证邀请码
if (!invitationService.isUserVerified(userId)) {
throw new BusinessException("请先验证邀请码后再使用对话功能");
}
// 继续处理对话请求
// ...
}
或者使用拦截器统一处理:
@Component
public class InvitationInterceptor implements HandlerInterceptor {
@Autowired
private InvitationService invitationService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 获取当前用户ID
Long userId = SecurityUtils.getUserId();
// 检查是否需要验证邀请码的接口
String uri = request.getRequestURI();
if (needsInvitationVerification(uri)) {
if (!invitationService.isUserVerified(userId)) {
throw new BusinessException("请先验证邀请码");
}
}
return true;
}
private boolean needsInvitationVerification(String uri) {
// 需要验证邀请码的接口列表
return uri.startsWith("/api/dialog/")
|| uri.startsWith("/api/task/");
}
}
3.5 后端文件清单
实体类
com.iccoder.entity.InvitationCode- 邀请码实体com.iccoder.entity.UserInvitation- 用户邀请码关联实体
Mapper
com.iccoder.mapper.InvitationCodeMapper- 邀请码数据访问com.iccoder.mapper.UserInvitationMapper- 用户邀请码关联数据访问
Service
com.iccoder.service.InvitationService- 邀请码业务逻辑com.iccoder.service.impl.InvitationServiceImpl- 实现类
Controller
com.iccoder.controller.InvitationController- 邀请码接口com.iccoder.controller.admin.InvitationAdminController- 管理接口
拦截器
com.iccoder.interceptor.InvitationInterceptor- 邀请码验证拦截器
四、用户体验优化
4.1 首次使用引导
在用户首次打开聊天面板时,如果未验证邀请码,显示友好的引导信息:
// 在 ICHelperPanel.ts 中
if (!await InvitationService.isVerified(context)) {
panel.webview.postMessage({
command: 'showInvitationGuide',
message: '欢迎使用 IC Coder!请先输入邀请码以开始使用。'
});
}
4.2 状态持久化
验证状态保存在 globalState 中,避免重复验证:
// 保存验证状态
await context.globalState.update('invitationCodeVerified', true);
await context.globalState.update('invitationCode', code);
await context.globalState.update('verifiedTime', new Date().toISOString());
4.3 错误提示优化
根据不同的错误类型,提供清晰的提示信息:
| 错误类型 | 提示信息 |
|---|---|
| 邀请码不存在 | "邀请码不存在,请检查后重新输入" |
| 邀请码已过期 | "邀请码已过期,请联系管理员获取新的邀请码" |
| 使用次数已达上限 | "该邀请码使用次数已达上限,请使用其他邀请码" |
| 已验证过 | "您已验证过邀请码,无需重复验证" |
| 网络错误 | "网络连接失败,请检查网络后重试" |
4.4 支持重新验证
提供命令允许用户更换邀请码:
// 在 extension.ts 中注册命令
context.subscriptions.push(
vscode.commands.registerCommand('ic-coder.changeInvitationCode', async () => {
const confirm = await vscode.window.showWarningMessage(
'确定要更换邀请码吗?',
'确定',
'取消'
);
if (confirm === '确定') {
await InvitationService.clearVerificationStatus(context);
vscode.window.showInformationMessage('已清除邀请码,请重新验证');
}
})
);
4.5 Webview 中显示状态(可选)
在聊天界面顶部显示邀请码验证状态:
<!-- 已验证 -->
<div class="invitation-status verified">
<span class="icon">✓</span>
<span>邀请码已验证</span>
</div>
<!-- 未验证 -->
<div class="invitation-status unverified">
<span class="icon">!</span>
<span>请先验证邀请码</span>
<button onclick="verifyInvitationCode()">立即验证</button>
</div>
五、安全考虑
5.1 邀请码生成规则
使用安全的随机算法生成邀请码:
public String generateInvitationCode() {
// 使用 UUID + 时间戳 + 随机数
String uuid = UUID.randomUUID().toString().replace("-", "");
String timestamp = String.valueOf(System.currentTimeMillis());
String random = RandomStringUtils.randomAlphanumeric(6);
// 组合并取前16位
String combined = uuid + timestamp + random;
String code = DigestUtils.sha256Hex(combined).substring(0, 16).toUpperCase();
return "IC" + code; // 添加前缀,例如:IC3F2A9B1C4D5E6F
}
5.2 防暴力破解
限制验证频率,添加验证失败次数限制:
// 使用 Redis 记录验证失败次数
String key = "invitation:fail:" + userId;
Integer failCount = redisTemplate.opsForValue().get(key);
if (failCount != null && failCount >= 5) {
throw new BusinessException("验证失败次数过多,请1小时后再试");
}
// 验证失败时增加计数
if (!verifySuccess) {
redisTemplate.opsForValue().increment(key);
redisTemplate.expire(key, 1, TimeUnit.HOURS);
}
5.3 Token 绑定
邀请码验证状态与用户 Token 绑定,退出登录时清除:
// 在退出登录时清除验证状态
vscode.commands.registerCommand('ic-coder.logout', async () => {
// 清除 session
await clearSession();
// 清除邀请码验证状态
await InvitationService.clearVerificationStatus(context);
vscode.window.showInformationMessage('已退出登录');
});
5.4 日志记录
记录所有验证尝试,便于审计和分析:
@Slf4j
public class InvitationServiceImpl implements InvitationService {
@Override
public boolean verifyInvitationCode(Long userId, String code) {
log.info("用户 {} 尝试验证邀请码: {}", userId, code);
try {
// 验证逻辑
// ...
log.info("用户 {} 验证邀请码成功: {}", userId, code);
return true;
} catch (Exception e) {
log.warn("用户 {} 验证邀请码失败: {}, 原因: {}",
userId, code, e.getMessage());
throw e;
}
}
}
5.5 敏感信息保护
- 邀请码在数据库中可以考虑加密存储(可选)
- API 响应中不暴露邀请码的详细信息(如剩余次数)
- 前端不缓存邀请码明文,只保存验证状态
六、实施步骤
阶段一:后端开发(优先)
- 创建数据库表
- 实现邀请码生成和管理功能
- 实现验证接口
- 添加权限拦截
- 测试接口功能
阶段二:前端开发
- 添加类型定义
- 实现
InvitationService - 修改
apiClient.ts添加接口调用 - 修改
messageHandler.ts添加验证检查 - 测试完整流程
阶段三:联调测试
- 前后端联调
- 测试各种异常场景
- 优化用户体验
- 性能测试
阶段四:上线部署
- 生成初始邀请码
- 更新用户文档
- 灰度发布
- 监控运行状态
七、测试用例
7.1 正常流程测试
| 测试场景 | 预期结果 |
|---|---|
| 首次使用,输入有效邀请码 | 验证成功,可以正常对话 |
| 已验证用户再次打开面板 | 无需重复验证,直接使用 |
| 退出登录后重新登录 | 需要重新验证邀请码 |
7.2 异常场景测试
| 测试场景 | 预期结果 |
|---|---|
| 输入不存在的邀请码 | 提示"邀请码不存在" |
| 输入已过期的邀请码 | 提示"邀请码已过期" |
| 输入使用次数已满的邀请码 | 提示"使用次数已达上限" |
| 已验证用户尝试再次验证 | 提示"已验证过,无需重复验证" |
| 网络断开时验证 | 提示"网络连接失败" |
| 连续输入错误邀请码5次 | 提示"验证失败次数过多,请稍后再试" |
7.3 边界条件测试
| 测试场景 | 预期结果 |
|---|---|
| 邀请码为空 | 前端验证拦截,提示"邀请码不能为空" |
| 邀请码长度不足 | 前端验证拦截,提示"邀请码格式不正确" |
| 邀请码包含特殊字符 | 后端验证失败,提示"邀请码不存在" |
| 同一邀请码多人同时使用 | 使用数据库锁,确保不超过最大次数 |
八、FAQ
Q1: 用户忘记邀请码怎么办?
A: 邀请码验证成功后,用户无需记住邀请码。如果需要查看,可以在设置中显示已验证的邀请码。
Q2: 邀请码可以重复使用吗?
A: 取决于邀请码的 maxUses 设置。可以设置为 1(一次性)、N(限定次数)或 -1(无限制)。
Q3: 如何批量生成邀请码?
A: 使用管理接口 POST /api/admin/invitation/generate,指定生成数量即可。
Q4: 邀请码验证失败会影响登录吗?
A: 不会。邀请码验证是独立的,只影响对话功能的使用,不影响登录。
Q5: 可以为不同用户群体设置不同的邀请码吗?
A: 可以。通过 remark 字段标记不同批次的邀请码,便于管理和统计。
九、后续优化方向
- 邀请码分级:不同等级的邀请码对应不同的权限(如对话次数、模型选择等)
- 邀请奖励:邀请他人使用可获得积分或额外权限
- 邀请统计:统计每个邀请码的使用情况和用户活跃度
- 自动过期:根据使用情况自动延长或缩短邀请码有效期
- 白名单机制:特定用户可以免邀请码使用
文档版本:v1.0 最后更新:2026-01-27 维护者:IC Coder Team