feat: 添加用户注册登录功能,实现数据和配置的用户隔离

- 新增 users 和 user_settings 表,repositories/analyses/review_notes 添加 user_id 列
- 实现基于邮箱+密码的注册登录,密码使用 bcrypt 哈希
- 使用 gin-contrib/sessions cookie-based session 管理
- 所有仓库、分析记录、review notes 按用户隔离
- 用户设置(LLM 配置、review 参数、缓存配置)独立存储
- 新增登录/注册页面,导航栏显示用户邮箱和退出按钮
- 前端 fetch 请求统一添加 credentials: 'same-origin'
- 支持 SESSION_SECRET 环境变量配置会话密钥
This commit is contained in:
2026-06-20 21:57:46 +08:00
parent 953c83d9aa
commit 9c843b79ba
28 changed files with 837 additions and 176 deletions
+68
View File
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="zh-CN" class="h-full">
{{template "head" .}}
<body class="h-full bg-gray-50 text-gray-900">
<div class="min-h-full flex flex-col">
{{template "nav" .}}
<main class="flex-1 flex items-center justify-center">
<div class="w-full max-w-md px-4">
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
<h1 class="text-2xl font-bold text-center mb-6">登录</h1>
<form id="login-form" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">邮箱</label>
<input type="email" name="email" required
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
placeholder="your@email.com">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">密码</label>
<input type="password" name="password" required
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
placeholder="••••••">
</div>
<div id="login-error" class="text-sm text-red-600 hidden"></div>
<button type="submit"
class="w-full bg-indigo-600 text-white px-5 py-2 rounded-md text-sm font-medium hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
登录
</button>
</form>
<p class="mt-4 text-center text-sm text-gray-500">
还没有账号?<a href="/register" class="text-indigo-600 hover:text-indigo-700">注册</a>
</p>
</div>
</div>
</main>
{{template "footer" .}}
</div>
<script>
document.getElementById('login-form').addEventListener('submit', async function(e) {
e.preventDefault();
const form = new FormData(this);
const errorEl = document.getElementById('login-error');
errorEl.classList.add('hidden');
try {
const resp = await fetch('/api/auth/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
credentials: 'same-origin',
body: JSON.stringify({
email: form.get('email'),
password: form.get('password')
})
});
const data = await resp.json();
if (!resp.ok) {
errorEl.textContent = data.error || '登录失败';
errorEl.classList.remove('hidden');
return;
}
window.location.href = '/';
} catch (err) {
errorEl.textContent = '网络错误,请重试';
errorEl.classList.remove('hidden');
}
});
</script>
</body>
</html>