Browse Source

开启热加载和一键启动

master
hejl 2 weeks ago
parent
commit
04d1dc9880
  1. 1
      .gitignore
  2. 149
      gofaster/app/LOGIN_FEATURE.md
  3. 222
      gofaster/app/LOGIN_IMPROVEMENTS.md
  4. 214
      gofaster/app/TEST_LOGIN.md
  5. 1541
      gofaster/app/dist/renderer/js/index.js
  6. 10
      gofaster/app/src/renderer/assets/themes.css
  7. 554
      gofaster/app/src/renderer/components/LoginModal.vue
  8. 200
      gofaster/app/src/renderer/components/MainLayout.vue
  9. 166
      gofaster/app/src/renderer/components/Toast.vue
  10. 10
      gofaster/app/src/renderer/services/userService.js
  11. 42
      gofaster/app/src/renderer/views/Home.vue
  12. BIN
      gofaster/backend/gofaster.exe
  13. 16
      gofaster/backend/internal/auth/module.go
  14. 17
      gofaster/backend/internal/auth/service/auth_service.go
  15. 63
      gofaster/backend/logs/app.log

1
.gitignore vendored

@ -11,3 +11,4 @@ @@ -11,3 +11,4 @@
/uft_dev_server/third_party
/gofaster/app/node_modules
/gofaster/app/dist
/gofaster/backend/logs

149
gofaster/app/LOGIN_FEATURE.md

@ -0,0 +1,149 @@ @@ -0,0 +1,149 @@
# GoFaster 登录功能说明
## 功能概述
GoFaster 前端应用已成功集成了完整的用户登录系统,包括:
- 🔐 登录弹窗组件
- 📱 响应式设计
- 🔒 验证码支持
- 💾 本地状态管理
- 🎨 主题适配
## 使用方法
### 1. 显示登录弹窗
登录弹窗可以通过以下两种方式触发:
#### 方式一:点击顶部导航栏的"登录"按钮
- 在未登录状态下,顶部导航栏会显示"🔐 登录"按钮
- 点击即可打开登录弹窗
#### 方式二:点击首页的登录按钮
- 在首页的欢迎区域,未登录状态下会显示登录提示
- 点击"立即登录"按钮即可打开登录弹窗
### 2. 登录流程
1. **获取验证码**:弹窗打开时自动获取验证码图片
2. **填写信息**
- 用户名:输入您的用户名
- 密码:输入您的密码
- 验证码:输入图片中显示的4位验证码
3. **提交登录**:点击"登录"按钮提交
4. **验证码刷新**:点击验证码图片可刷新获取新的验证码
### 3. 默认账号
系统提供了默认的管理员账号用于测试:
- **用户名**:`sysadmin`
- **密码**:`sysadmin@123`
## 技术实现
### 组件结构
```
MainLayout.vue (主布局)
├── LoginModal.vue (登录弹窗)
├── Toast.vue (消息提示)
└── 其他组件...
```
### 状态管理
- 使用 Vue 3 Composition API
- 通过 `provide/inject` 在组件间共享登录状态
- 本地存储用户信息和登录状态
### 后端接口
登录功能需要后端提供以下接口:
1. **获取验证码**:`GET /api/auth/captcha`
2. **用户登录**:`POST /api/auth/login`
### 配置说明
前端配置位于 `src/config/app.config.js`,默认连接 `http://localhost:8080/api`
## 样式特性
### 主题适配
- 支持深色/浅色主题切换
- 使用 CSS 变量实现主题一致性
- 响应式设计,支持移动端
### 动画效果
- 弹窗滑入动画
- 加载状态指示器
- 平滑的过渡效果
## 错误处理
- 网络连接失败提示
- 验证码错误处理
- 登录失败重试机制
- 表单验证提示
## 安全特性
- 密码输入框隐藏内容
- 验证码防机器人
- Token 认证机制
- 自动登出处理
## 开发说明
### 添加新的登录相关功能
1. 在 `LoginModal.vue` 中添加新的表单字段
2. 在 `userService.js` 中添加对应的 API 调用
3. 在 `MainLayout.vue` 中处理新的状态变化
### 自定义样式
登录弹窗的样式可以通过修改 `LoginModal.vue` 中的 CSS 变量来调整:
```css
:root {
--primary-color: #1976d2;
--primary-hover: #1565c0;
--bg-primary: #ffffff;
--text-primary: #2c3e50;
}
```
## 故障排除
### 常见问题
1. **登录弹窗不显示**
- 检查浏览器控制台是否有错误
- 确认组件是否正确导入
2. **验证码获取失败**
- 检查后端服务是否正常运行
- 确认网络连接正常
3. **登录失败**
- 检查用户名和密码是否正确
- 确认验证码输入正确
- 查看后端日志获取详细错误信息
### 调试模式
在开发环境中,可以打开浏览器开发者工具查看:
- 网络请求状态
- 控制台日志
- Vue 组件状态
## 更新日志
- **v1.0.0**:初始版本,包含基本的登录功能
- 支持用户名/密码登录
- 集成验证码系统
- 响应式设计
- 主题适配

222
gofaster/app/LOGIN_IMPROVEMENTS.md

@ -0,0 +1,222 @@ @@ -0,0 +1,222 @@
# GoFaster 登录功能改进说明
## 🎯 问题修复
### 1. 验证码获取失败问题 ✅
**问题描述**:
- 验证码接口调用失败时,用户没有清晰的反馈
- 错误处理不够完善
**解决方案**:
- 添加了 `captchaLoading` 状态管理
- 改进了错误处理和用户提示
- 添加了验证码加载动画
- 在控制台输出详细的调试信息
**具体改进**:
```javascript
// 添加验证码加载状态
captchaLoading: false
// 改进错误处理
async refreshCaptcha() {
try {
this.captchaLoading = true
this.errorMessage = ''
const response = await userService.getCaptcha()
// ... 处理成功响应
} catch (error) {
// ... 处理错误
} finally {
this.captchaLoading = false
}
}
```
### 2. 登录按钮显示逻辑问题 ✅
**问题描述**:
- 用户不清楚为什么登录按钮被禁用
- 缺少表单验证的实时反馈
**解决方案**:
- 修改了 `isFormValid` 计算属性,确保验证码图片加载后才启用按钮
- 添加了实时表单验证提示
- 提供了清晰的用户指导
**具体改进**:
```javascript
// 改进的表单验证逻辑
isFormValid() {
return this.loginForm.username.trim() &&
this.loginForm.password.trim() &&
this.loginForm.captcha.trim() &&
this.captchaImage // 确保验证码图片已加载
}
// 添加表单验证提示
<div v-if="!isFormValid && (loginForm.username || loginForm.password || loginForm.captcha)" class="form-validation-hint">
<span v-if="!loginForm.username.trim()">请填写用户名</span>
<span v-else-if="!loginForm.password.trim()">请填写密码</span>
<span v-else-if="!loginForm.captcha.trim()">请填写验证码</span>
<span v-else-if="!captchaImage">请先获取验证码</span>
</div>
```
## 🎨 用户体验改进
### 1. 验证码状态指示
- **加载中**:显示"验证码加载中..."和旋转动画
- **获取失败**:显示"点击获取验证码"和错误提示
- **获取成功**:显示验证码图片
### 2. 表单验证反馈
- 实时显示缺少的字段信息
- 清晰的错误提示信息
- 智能的表单状态管理
### 3. 交互优化
- 验证码占位符支持悬停效果
- 加载状态动画
- 更好的视觉反馈
## 🔧 技术实现细节
### 状态管理
```javascript
data() {
return {
loading: false, // 登录加载状态
captchaLoading: false, // 验证码加载状态
errorMessage: '', // 错误消息
captchaImage: '', // 验证码图片
captchaId: '', // 验证码ID
loginForm: { // 登录表单数据
username: '',
password: '',
captcha: ''
}
}
}
```
### 计算属性
```javascript
computed: {
isFormValid() {
// 所有字段都必须填写,且验证码图片必须已加载
return this.loginForm.username.trim() &&
this.loginForm.password.trim() &&
this.loginForm.captcha.trim() &&
this.captchaImage
}
}
```
### 样式改进
```css
/* 表单验证提示样式 */
.form-validation-hint {
margin-top: 8px;
padding: 8px 12px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 12px;
color: var(--text-secondary);
text-align: center;
}
/* 验证码加载动画 */
.captcha-loading-spinner {
width: 12px;
height: 12px;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-top: 4px;
}
```
## 📱 响应式设计
### 移动端适配
- 验证码容器在小屏幕下垂直排列
- 表单提示信息自适应屏幕宽度
- 触摸友好的交互设计
### 主题适配
- 支持深色/浅色主题切换
- 使用CSS变量确保一致性
- 悬停效果和过渡动画
## 🧪 测试建议
### 功能测试
1. **验证码获取**
- 点击验证码区域
- 观察加载状态
- 检查错误处理
2. **表单验证**
- 逐个填写字段
- 观察按钮状态变化
- 验证提示信息
3. **登录流程**
- 完整填写表单
- 测试登录按钮启用
- 验证成功/失败处理
### 边界情况测试
- 网络连接失败
- 验证码接口错误
- 表单数据不完整
- 快速点击操作
## 🚀 后续优化建议
### 1. 性能优化
- 验证码图片缓存
- 防抖处理
- 懒加载优化
### 2. 功能扩展
- 记住用户名
- 自动登录
- 多因素认证
### 3. 用户体验
- 键盘快捷键支持
- 语音输入
- 无障碍访问
## 📝 更新日志
### v1.1.0 (2025-08-23)
- ✅ 修复验证码获取失败问题
- ✅ 改进登录按钮显示逻辑
- ✅ 添加表单验证实时反馈
- ✅ 优化验证码状态指示
- ✅ 改进错误处理和用户提示
- ✅ 添加加载状态动画
- ✅ 优化响应式设计
### v1.0.0 (2025-08-23)
- 🎉 初始版本发布
- 🔐 基础登录功能
- 🎨 美观的UI设计
- 📱 响应式布局
## 🎊 总结
通过这次改进,GoFaster 的登录功能现在具有:
1. **更好的用户体验**:清晰的验证码状态指示和表单验证反馈
2. **更稳定的功能**:改进的错误处理和状态管理
3. **更美观的界面**:加载动画和悬停效果
4. **更智能的逻辑**:验证码加载完成后才启用登录按钮
这些改进让用户能够更清楚地了解登录流程的每个步骤,提高了整体的可用性和满意度!🎉

214
gofaster/app/TEST_LOGIN.md

@ -0,0 +1,214 @@ @@ -0,0 +1,214 @@
# GoFaster 登录功能测试指南
## 🚀 快速开始
### 1. 启动应用
```bash
# 在 app 目录下运行
npm run dev
```
### 2. 测试登录弹窗显示
#### 方式一:顶部导航栏登录按钮
- 应用启动后,顶部导航栏右侧会显示"🔐 登录"按钮
- 点击按钮,登录弹窗应该优雅地滑入显示
#### 方式二:首页登录按钮
- 在首页欢迎区域,未登录状态下会显示"立即登录"按钮
- 点击按钮,同样会显示登录弹窗
### 3. 测试登录弹窗功能
#### 验证码获取
- 弹窗打开时应该自动获取验证码图片
- 如果验证码图片显示失败,会显示"点击获取验证码"的占位符
- 点击验证码区域可以刷新获取新的验证码
#### 表单验证
- 尝试不填写任何信息直接点击登录按钮
- 应该显示"请填写完整的登录信息"错误提示
- 填写部分信息后,登录按钮应该保持禁用状态
#### 登录流程测试
1. **输入测试账号**
- 用户名:`sysadmin`
- 密码:`sysadmin@123`
- 验证码:输入图片中显示的4位数字
2. **点击登录按钮**
- 按钮应该显示"登录中..."状态
- 显示加载动画(旋转的圆圈)
3. **登录结果**
- 如果后端服务正常运行,应该显示登录成功提示
- 如果后端服务未运行,会显示网络连接失败错误
## 🔧 后端服务要求
### 必需的后端接口
1. **验证码接口**
```
GET /api/auth/captcha
响应格式:
{
"captchaImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"captchaID": "abc123"
}
```
2. **登录接口**
```
POST /api/auth/login
请求格式:
{
"username": "sysadmin",
"password": "sysadmin@123",
"captcha": "1234",
"captchaId": "abc123"
}
响应格式:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"username": "sysadmin",
"email": "admin@gofaster.com",
"avatar": null
}
}
```
### 启动后端服务
```bash
# 在 backend 目录下运行
.\gofaster.exe
```
## 🎯 测试检查点
### ✅ 功能检查
- [ ] 登录弹窗能正常显示
- [ ] 验证码能正常获取和显示
- [ ] 表单验证正常工作
- [ ] 登录按钮状态正确
- [ ] 错误提示信息正确显示
- [ ] 登录成功后弹窗关闭
- [ ] 用户信息正确更新
- [ ] Toast 消息提示正常显示
### ✅ 样式检查
- [ ] 弹窗动画流畅
- [ ] 响应式设计正常
- [ ] 主题适配正确
- [ ] 加载状态指示器正常
- [ ] 错误提示样式正确
### ✅ 交互检查
- [ ] 点击遮罩层可以关闭弹窗
- [ ] 点击关闭按钮可以关闭弹窗
- [ ] 验证码点击可以刷新
- [ ] 表单输入响应正常
- [ ] 键盘操作支持(Enter 提交)
## 🐛 常见问题排查
### 1. 登录弹窗不显示
**可能原因**:
- 组件导入错误
- 方法名冲突
- 事件绑定失败
**排查步骤**:
- 检查浏览器控制台是否有错误
- 确认 `showLoginModal` 方法是否正确注入
- 验证点击事件是否正确绑定
### 2. 验证码获取失败
**可能原因**:
- 后端服务未启动
- 网络连接问题
- API 接口路径错误
**排查步骤**:
- 确认后端服务是否正常运行
- 检查网络请求是否成功
- 验证 API 基础 URL 配置
### 3. 登录失败
**可能原因**:
- 用户名或密码错误
- 验证码输入错误
- 后端认证逻辑问题
**排查步骤**:
- 确认测试账号信息正确
- 检查验证码是否输入正确
- 查看后端日志获取详细错误信息
## 📱 响应式测试
### 桌面端测试
- 窗口大小调整时弹窗布局正常
- 不同分辨率下显示效果一致
### 移动端测试
- 在小屏幕设备上弹窗适配正常
- 触摸操作响应正常
- 虚拟键盘弹出时布局正常
## 🎨 主题测试
### 深色主题
- 弹窗在深色主题下显示正常
- 颜色对比度合适
### 浅色主题
- 弹窗在浅色主题下显示正常
- 文字清晰可读
## 📝 测试报告模板
```
测试日期:_________
测试环境:_________
测试人员:_________
## 功能测试结果
- [ ] 登录弹窗显示:□通过 □失败
- [ ] 验证码获取:□通过 □失败
- [ ] 表单验证:□通过 □失败
- [ ] 登录流程:□通过 □失败
- [ ] 错误处理:□通过 □失败
## 样式测试结果
- [ ] 动画效果:□通过 □失败
- [ ] 响应式设计:□通过 □失败
- [ ] 主题适配:□通过 □失败
## 发现的问题
1. ________________
2. ________________
## 建议改进
1. ________________
2. ________________
## 总体评价
□优秀 □良好 □一般 □需要改进
```
## 🎉 成功标准
当您看到以下现象时,说明登录功能已经成功实现:
1. ✅ 点击登录按钮,弹窗优雅滑入
2. ✅ 验证码图片正常显示
3. ✅ 表单验证提示正确
4. ✅ 登录成功后显示欢迎消息
5. ✅ 顶部导航栏显示用户头像
6. ✅ 首页显示个性化欢迎信息
7. ✅ 可以使用所有功能模块
恭喜!您的 GoFaster 应用已经成功集成了完整的用户登录系统!🎊

1541
gofaster/app/dist/renderer/js/index.js vendored

File diff suppressed because it is too large Load Diff

10
gofaster/app/src/renderer/assets/themes.css

@ -11,6 +11,8 @@ @@ -11,6 +11,8 @@
--border-color: #e0e0e0;
--accent-color: #1976d2;
--accent-hover: #1565c0;
--primary-color: #1976d2;
--primary-hover: #1565c0;
--success-color: #4caf50;
--warning-color: #ff9800;
--error-color: #f44336;
@ -27,6 +29,9 @@ @@ -27,6 +29,9 @@
--tab-bg: #f8f9fa;
--tab-active-bg: #007bff;
--tab-active-text: #ffffff;
--error-bg: #ffebee;
--error-text: #c62828;
--error-border: #ffcdd2;
}
/* 深色主题 */
@ -40,6 +45,8 @@ @@ -40,6 +45,8 @@
--border-color: rgba(255, 255, 255, 0.2);
--accent-color: #ffffff;
--accent-hover: #e0e0e0;
--primary-color: #ffffff;
--primary-hover: #e0e0e0;
--success-color: #4caf50;
--warning-color: #ff9800;
--error-color: #f44336;
@ -56,6 +63,9 @@ @@ -56,6 +63,9 @@
--tab-bg: #1a2a4a;
--tab-active-bg: #2d4a8a;
--tab-active-text: #ffffff;
--error-bg: rgba(244, 67, 54, 0.15);
--error-text: #ff8a80;
--error-border: rgba(244, 67, 54, 0.4);
}
/* 主题切换过渡动画 */

554
gofaster/app/src/renderer/components/LoginModal.vue

@ -0,0 +1,554 @@ @@ -0,0 +1,554 @@
<template>
<div v-if="visible" class="login-modal-overlay" @click="handleOverlayClick">
<div class="login-modal" @click.stop>
<div class="login-modal-header">
<h2>用户登录</h2>
<button class="close-btn" @click="closeModal">×</button>
</div>
<div class="login-modal-body">
<form @submit.prevent="handleLogin" class="login-form">
<!-- 用户名 -->
<div class="form-group">
<label for="username">用户名</label>
<input
id="username"
v-model="loginForm.username"
type="text"
placeholder="请输入用户名"
required
:disabled="loading"
/>
</div>
<!-- 密码 -->
<div class="form-group">
<label for="password">密码</label>
<input
id="password"
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
required
:disabled="loading"
/>
</div>
<!-- 验证码 -->
<div class="form-group">
<label for="captcha">验证码</label>
<div class="captcha-container">
<input
id="captcha"
v-model="loginForm.captcha"
type="text"
placeholder="请输入验证码"
required
:disabled="loading"
maxlength="4"
/>
<div class="captcha-image-container">
<img
v-if="captchaImage"
:src="captchaImage"
alt="验证码"
@click="refreshCaptcha"
class="captcha-image"
/>
<div v-else class="captcha-placeholder" @click="refreshCaptcha">
<span v-if="captchaLoading">验证码加载中...</span>
<span v-else>点击获取验证码</span>
<span v-if="captchaLoading" class="captcha-loading-spinner"></span>
</div>
</div>
</div>
</div>
<!-- 登录按钮 -->
<div class="form-actions">
<button
type="submit"
class="login-btn"
:disabled="loading || !isFormValid"
>
<span v-if="loading" class="loading-spinner"></span>
{{ loading ? '登录中...' : '登录' }}
</button>
<!-- 表单验证提示 -->
<div v-if="!isFormValid && (loginForm.username || loginForm.password || loginForm.captcha)" class="form-validation-hint">
<span v-if="!loginForm.username.trim()">请填写用户名</span>
<span v-else-if="!loginForm.password.trim()">请填写密码</span>
<span v-else-if="!loginForm.captcha.trim()">请填写验证码</span>
<span v-else-if="!captchaImage">请先获取验证码</span>
</div>
</div>
<!-- 错误提示 -->
<div v-if="errorMessage" class="error-message">
{{ errorMessage }}
</div>
</form>
</div>
<div class="login-modal-footer">
<p class="login-tips">
<span>提示</span>
<span>默认管理员账号sysadmin</span>
<span>默认密码sysadmin@123</span>
</p>
</div>
</div>
</div>
</template>
<script>
import { userService } from '../services/userService'
export default {
name: 'LoginModal',
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
loading: false,
captchaLoading: false,
errorMessage: '',
captchaImage: '',
captchaId: '',
loginForm: {
username: '',
password: '',
captcha: ''
}
}
},
computed: {
isFormValid() {
return this.loginForm.username.trim() &&
this.loginForm.password.trim() &&
this.loginForm.captcha.trim() &&
this.captchaImage //
}
},
watch: {
visible(newVal) {
if (newVal) {
this.resetForm()
this.refreshCaptcha()
}
}
},
methods: {
async handleLogin() {
if (!this.isFormValid) {
this.errorMessage = '请填写完整的登录信息'
return
}
this.loading = true
this.errorMessage = ''
try {
//
const response = await userService.login({
username: this.loginForm.username,
password: this.loginForm.password,
captcha: this.loginForm.captcha,
captchaId: this.captchaId
})
//
this.$emit('login-success', response)
this.closeModal()
//
this.$emit('show-message', {
type: 'success',
title: '登录成功',
content: `欢迎回来,${response.user?.username || '用户'}`
})
} catch (error) {
this.errorMessage = error.message || '登录失败,请重试'
//
this.refreshCaptcha()
} finally {
this.loading = false
}
},
async refreshCaptcha() {
try {
this.captchaLoading = true
this.errorMessage = ''
//
const response = await userService.getCaptcha()
this.captchaImage = response.captchaImage
this.captchaId = response.captchaID
this.loginForm.captcha = ''
console.log('验证码获取成功:', response)
} catch (error) {
console.error('获取验证码失败:', error)
this.errorMessage = '获取验证码失败,请重试'
this.captchaImage = ''
this.captchaId = ''
} finally {
this.captchaLoading = false
}
},
resetForm() {
this.loginForm = {
username: '',
password: '',
captcha: ''
}
this.errorMessage = ''
this.captchaImage = ''
this.captchaId = ''
},
closeModal() {
this.$emit('update:visible', false)
this.resetForm()
},
handleOverlayClick() {
this.closeModal()
}
}
}
</script>
<style scoped>
.login-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(4px);
}
.login-modal {
background: var(--bg-primary);
border-radius: 12px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
width: 90%;
max-width: 400px;
max-height: 90vh;
overflow: hidden;
animation: modalSlideIn 0.3s ease-out;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.login-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-bottom: 1px solid var(--border-color);
background: var(--header-bg);
}
.login-modal-header h2 {
margin: 0;
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
}
.close-btn {
background: none;
border: none;
font-size: 24px;
color: var(--text-secondary);
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
}
.close-btn:hover {
background: var(--bg-secondary);
color: var(--text-primary);
}
.login-modal-body {
padding: 24px;
}
.login-form {
display: flex;
flex-direction: column;
gap: 20px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-group label {
font-weight: 500;
color: var(--text-primary);
font-size: 14px;
}
.form-group input {
padding: 12px 16px;
border: 2px solid var(--border-color);
border-radius: 8px;
font-size: 14px;
background: var(--bg-secondary);
color: var(--text-primary);
transition: all 0.2s;
}
.form-group input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);
}
.form-group input:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.captcha-container {
display: flex;
gap: 12px;
align-items: stretch;
}
.captcha-container input {
flex: 1;
}
.captcha-image-container {
width: 100px;
height: 44px;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
border: 2px solid var(--border-color);
transition: all 0.2s;
}
.captcha-image-container:hover {
border-color: var(--primary-color);
}
.captcha-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.captcha-placeholder {
width: 100%;
height: 100%;
background: var(--bg-secondary);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--text-secondary);
font-size: 12px;
text-align: center;
padding: 8px;
cursor: pointer;
transition: all 0.2s;
}
.captcha-placeholder:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.captcha-hint {
font-size: 10px;
opacity: 0.7;
margin-top: 2px;
}
.captcha-loading-spinner {
width: 12px;
height: 12px;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-top: 4px;
}
.form-actions {
margin-top: 8px;
}
.login-btn {
width: 100%;
padding: 14px 24px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
/* 深色主题下的登录按钮样式 */
.theme-dark .login-btn {
background: var(--primary-color);
color: #333333;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.theme-dark .login-btn:hover:not(:disabled) {
background: var(--primary-hover);
color: #222222;
}
.login-btn:hover:not(:disabled) {
background: var(--primary-hover);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.3);
}
.login-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
/* 表单验证提示样式 */
.form-validation-hint {
margin-top: 8px;
padding: 8px 12px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 12px;
color: var(--text-secondary);
text-align: center;
}
.form-validation-hint span {
display: block;
margin-bottom: 2px;
}
.form-validation-hint span:last-child {
margin-bottom: 0;
}
.loading-spinner {
width: 16px;
height: 16px;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.error-message {
background: var(--error-bg);
color: var(--error-text);
padding: 12px 16px;
border-radius: 8px;
font-size: 14px;
text-align: center;
border: 1px solid var(--error-border);
}
/* 深色主题下的错误提示样式优化 */
.theme-dark .error-message {
background: rgba(244, 67, 54, 0.2);
color: #ffab91;
border: 1px solid rgba(244, 67, 54, 0.5);
font-weight: 500;
}
.login-modal-footer {
padding: 16px 24px;
border-top: 1px solid var(--border-color);
background: var(--bg-secondary);
}
.login-tips {
margin: 0;
font-size: 12px;
color: var(--text-secondary);
text-align: center;
line-height: 1.5;
}
.login-tips span {
display: block;
margin-bottom: 4px;
}
.login-tips span:first-child {
font-weight: 600;
color: var(--text-primary);
}
/* 响应式设计 */
@media (max-width: 480px) {
.login-modal {
width: 95%;
margin: 20px;
}
.login-modal-header,
.login-modal-body,
.login-modal-footer {
padding: 16px 20px;
}
.captcha-container {
flex-direction: column;
gap: 8px;
}
.captcha-image-container {
width: 100%;
height: 40px;
}
}
</style>

200
gofaster/app/src/renderer/components/MainLayout.vue

@ -45,13 +45,20 @@ @@ -45,13 +45,20 @@
<!-- 用户信息 -->
<div class="user-info">
<div class="user-avatar" @click="toggleUserMenu">
<div v-if="isLoggedIn" class="user-avatar" @click="toggleUserMenu">
<img v-if="currentUser.avatar" :src="currentUser.avatar" :alt="currentUser.name" />
<span v-else class="avatar-placeholder">{{ currentUser.name?.charAt(0) || 'U' }}</span>
</div>
<!-- 未登录状态显示登录按钮 -->
<div v-else class="login-section">
<button class="login-btn" @click="showLoginModal">
<i class="icon">🔐</i> 登录
</button>
</div>
<!-- 用户菜单 -->
<div v-if="showUserMenu" class="user-menu">
<div v-if="showUserMenu && isLoggedIn" class="user-menu">
<div class="user-menu-header">
<div class="user-details">
<div class="user-name">{{ currentUser.name || '用户' }}</div>
@ -62,9 +69,9 @@ @@ -62,9 +69,9 @@
<button class="menu-item" @click="openProfile">
<i class="icon">👤</i> 个人资料
</button>
<button class="menu-item" @click="openSettings">
<i class="icon"></i> 用户设置
</button>
<button class="menu-item" @click="openSettings">
<i class="icon"></i> 用户设置
</button>
<button class="menu-item" @click="logout">
<i class="icon">🚪</i> 退出登录
</button>
@ -165,6 +172,22 @@ @@ -165,6 +172,22 @@
</div>
</div>
</div>
<!-- 登录弹窗 -->
<LoginModal
v-model:visible="showLoginModalFlag"
@login-success="handleLoginSuccess"
@show-message="handleShowMessage"
/>
<!-- Toast 消息提示 -->
<Toast
v-model:visible="showToast"
:type="toastConfig.type"
:title="toastConfig.title"
:content="toastConfig.content"
:duration="toastConfig.duration"
/>
</div>
</template>
@ -173,9 +196,22 @@ import { ref, reactive, computed, onMounted, watch } from 'vue' @@ -173,9 +196,22 @@ import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { userService } from '@/services/userService'
import themeManager from '../utils/themeManager.js'
import LoginModal from './LoginModal.vue'
import Toast from './Toast.vue'
export default {
name: 'MainLayout',
components: {
LoginModal,
Toast
},
provide() {
return {
isLoggedIn: this.isLoggedIn,
currentUser: this.currentUser,
showLoginModal: this.showLoginModal
}
},
setup() {
const router = useRouter()
const route = useRoute()
@ -183,21 +219,34 @@ export default { @@ -183,21 +219,34 @@ export default {
//
const showMessagePanel = ref(false)
const showUserMenu = ref(false)
const showLoginModalFlag = ref(false)
const showToast = ref(false)
const currentTab = ref('home')
const openTabs = ref([
{ id: 'home', title: '欢迎', path: '/', closable: false }
])
const currentUser = reactive({
name: '管理员',
email: 'admin@gofaster.com',
name: '',
email: '',
avatar: null
})
//
const isLoggedIn = ref(false)
//
const appSettings = reactive({
appName: 'GoFaster'
})
// Toast
const toastConfig = reactive({
type: 'info',
title: '',
content: '',
duration: 3000
})
const messages = ref([
{
@ -353,10 +402,63 @@ export default { @@ -353,10 +402,63 @@ export default {
addTabIfNotExists(tabInfo)
}
const showLoginModal = () => {
showLoginModalFlag.value = true
showUserMenu.value = false
}
const handleLoginSuccess = (response) => {
//
if (response.user) {
currentUser.name = response.user.username || response.user.name
currentUser.email = response.user.email
currentUser.avatar = response.user.avatar
}
//
isLoggedIn.value = true
//
localStorage.setItem('user', JSON.stringify(currentUser))
localStorage.setItem('isLoggedIn', 'true')
//
showLoginModalFlag.value = false
//
console.log(`欢迎回来,${currentUser.name}`)
}
const handleShowMessage = (message) => {
// Toast
toastConfig.type = message.type || 'info'
toastConfig.title = message.title || '消息'
toastConfig.content = message.content || ''
toastConfig.duration = message.duration || 3000
// Toast
showToast.value = true
}
const logout = async () => {
try {
await userService.logout()
router.push('/login')
//
currentUser.name = ''
currentUser.email = ''
currentUser.avatar = null
isLoggedIn.value = false
//
localStorage.removeItem('user')
localStorage.removeItem('isLoggedIn')
localStorage.removeItem('token')
//
showUserMenu.value = false
//
router.push('/')
} catch (error) {
console.error('退出登录失败:', error)
}
@ -415,10 +517,21 @@ export default { @@ -415,10 +517,21 @@ export default {
//
updateFavoriteMenu()
//
//
const savedIsLoggedIn = localStorage.getItem('isLoggedIn')
const savedUser = localStorage.getItem('user')
if (savedUser) {
Object.assign(currentUser, JSON.parse(savedUser))
if (savedIsLoggedIn === 'true' && savedUser) {
try {
const userData = JSON.parse(savedUser)
Object.assign(currentUser, userData)
isLoggedIn.value = true
} catch (error) {
console.warn('解析用户信息失败:', error)
//
localStorage.removeItem('user')
localStorage.removeItem('isLoggedIn')
}
}
//
@ -436,15 +549,24 @@ export default { @@ -436,15 +549,24 @@ export default {
console.warn('加载用户主题设置失败:', error)
}
}
//
window.addEventListener('show-login-modal', () => {
showLoginModalFlag.value = true
})
})
return {
showMessagePanel,
showUserMenu,
showLoginModalFlag,
showToast,
currentTab,
openTabs,
currentUser,
isLoggedIn,
appSettings,
toastConfig,
messages,
mainMenuItems,
favoriteMenuItems,
@ -453,6 +575,7 @@ export default { @@ -453,6 +575,7 @@ export default {
breadcrumbs,
toggleMessagePanel,
toggleUserMenu,
showLoginModal,
handleMenuClick,
switchTab,
closeTab,
@ -461,11 +584,13 @@ export default { @@ -461,11 +584,13 @@ export default {
markAsRead,
formatTime,
openProfile,
openSettings,
logout,
loadAppSettings,
handleAddTab,
addTabIfNotExists
openSettings,
logout,
handleLoginSuccess,
handleShowMessage,
loadAppSettings,
handleAddTab,
addTabIfNotExists
}
}
}
@ -1010,6 +1135,49 @@ export default { @@ -1010,6 +1135,49 @@ export default {
overflow: visible;
}
/* 登录按钮样式 */
.login-section {
display: flex;
align-items: center;
}
.login-btn {
background: var(--primary-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 6px;
}
/* 深色主题下的登录按钮样式 */
.theme-dark .login-btn {
background: var(--primary-color);
color: #333333;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.theme-dark .login-btn:hover {
background: var(--primary-hover);
color: #222222;
}
.login-btn:hover {
background: var(--primary-hover);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(25, 118, 210, 0.3);
}
.login-btn .icon {
font-size: 16px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.sidebar {

166
gofaster/app/src/renderer/components/Toast.vue

@ -0,0 +1,166 @@ @@ -0,0 +1,166 @@
<template>
<transition name="toast">
<div v-if="visible" class="toast" :class="type">
<div class="toast-icon">{{ icon }}</div>
<div class="toast-content">
<div class="toast-title">{{ title }}</div>
<div v-if="content" class="toast-message">{{ content }}</div>
</div>
<button class="toast-close" @click="close">×</button>
</div>
</transition>
</template>
<script>
export default {
name: 'Toast',
props: {
visible: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'info',
validator: value => ['success', 'error', 'warning', 'info'].includes(value)
},
title: {
type: String,
required: true
},
content: {
type: String,
default: ''
},
duration: {
type: Number,
default: 3000
}
},
computed: {
icon() {
const icons = {
success: '✅',
error: '❌',
warning: '⚠',
info: 'ℹ'
}
return icons[this.type] || icons.info
}
},
watch: {
visible(newVal) {
if (newVal && this.duration > 0) {
setTimeout(() => {
this.close()
}, this.duration)
}
}
},
methods: {
close() {
this.$emit('update:visible', false)
}
}
}
</script>
<style scoped>
.toast {
position: fixed;
top: 20px;
right: 20px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 16px;
min-width: 300px;
max-width: 400px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 10000;
display: flex;
align-items: flex-start;
gap: 12px;
}
.toast.success {
border-left: 4px solid #4caf50;
}
.toast.error {
border-left: 4px solid #f44336;
}
.toast.warning {
border-left: 4px solid #ff9800;
}
.toast.info {
border-left: 4px solid #2196f3;
}
.toast-icon {
font-size: 20px;
flex-shrink: 0;
}
.toast-content {
flex: 1;
min-width: 0;
}
.toast-title {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
font-size: 14px;
}
.toast-message {
color: var(--text-secondary);
font-size: 13px;
line-height: 1.4;
}
.toast-close {
background: none;
border: none;
color: var(--text-secondary);
font-size: 18px;
cursor: pointer;
padding: 2px;
border-radius: 4px;
transition: all 0.2s;
flex-shrink: 0;
}
.toast-close:hover {
background: var(--bg-secondary);
color: var(--text-primary);
}
/* 动画 */
.toast-enter-active,
.toast-leave-active {
transition: all 0.3s ease;
}
.toast-enter-from {
opacity: 0;
transform: translateX(100%);
}
.toast-leave-to {
opacity: 0;
transform: translateX(100%);
}
/* 响应式 */
@media (max-width: 480px) {
.toast {
left: 20px;
right: 20px;
min-width: auto;
}
}
</style>

10
gofaster/app/src/renderer/services/userService.js

@ -136,6 +136,16 @@ export const userService = { @@ -136,6 +136,16 @@ export const userService = {
}
},
// 获取验证码
async getCaptcha() {
try {
const response = await api.get('/auth/captcha')
return response
} catch (error) {
throw error
}
},
// 用户登录
async login(credentials) {
try {

42
gofaster/app/src/renderer/views/Home.vue

@ -8,16 +8,16 @@ @@ -8,16 +8,16 @@
<div class="welcome-card">
<div class="welcome-header">
<div class="welcome-text">
<h2 v-if="isLoggedIn">欢迎回来{{ userInfo.name }}</h2>
<h2 v-if="isLoggedIn">欢迎回来{{ currentUser?.name || userInfo.name }}</h2>
<h2 v-else>欢迎光临请登录</h2>
<p v-if="isLoggedIn" class="subtitle">今天是 {{ currentDate }}您有 {{ todoList.length }} 个待办事项</p>
<p v-else class="subtitle">登录后享受更多功能</p>
</div>
<div v-if="!isLoggedIn" class="login-section">
<button class="login-btn" @click="showLoginModal">
<i class="icon">🔐</i> 登录
</button>
</div>
<div v-if="!isLoggedIn" class="login-section">
<button class="login-btn" @click="showLoginModalHandler">
<i class="icon">🔐</i> 登录
</button>
</div>
</div>
</div>
</section>
@ -52,9 +52,9 @@ @@ -52,9 +52,9 @@
<div class="prompt-icon">🔒</div>
<h3>需要登录</h3>
<p>登录后即可使用所有功能</p>
<button class="prompt-login-btn" @click="showLoginModal">
<i class="icon">🔐</i> 立即登录
</button>
<button class="prompt-login-btn" @click="showLoginModalHandler">
<i class="icon">🔐</i> 立即登录
</button>
</div>
</section>
@ -103,15 +103,15 @@ @@ -103,15 +103,15 @@
<script>
export default {
name: 'HomeView',
data() {
inject: ['isLoggedIn', 'currentUser', 'showLoginModal'],
data() {
return {
userInfo: {
name: '用户',
email: 'user@example.com',
avatar: '👤'
},
isLoggedIn: false, //
currentDate: '',
currentDate: '',
stats: {
totalTests: 0,
avgSpeed: '0 Mbps',
@ -203,10 +203,12 @@ export default { @@ -203,10 +203,12 @@ export default {
exportData() {
console.log('导出数据');
},
showLoginModal() {
//
this.$emit('show-login-modal');
},
showLoginModalHandler() {
//
if (this.showLoginModal) {
this.showLoginModal();
}
},
async addTodo() {
const newTodo = {
id: Date.now(),
@ -272,6 +274,14 @@ export default { @@ -272,6 +274,14 @@ export default {
} catch (error) {
console.error('数据库初始化失败:', error);
}
//
this.$watch('isLoggedIn', (newVal) => {
if (newVal && this.currentUser) {
this.userInfo.name = this.currentUser.name || this.userInfo.name;
this.userInfo.email = this.currentUser.email || this.userInfo.email;
}
}, { immediate: true });
}
}
</script>

BIN
gofaster/backend/gofaster.exe

Binary file not shown.

16
gofaster/backend/internal/auth/module.go

@ -2,9 +2,11 @@ package auth @@ -2,9 +2,11 @@ package auth
import (
"gofaster/internal/auth/migration"
"gofaster/internal/auth/routes"
"gofaster/internal/core"
"gofaster/internal/shared/config"
"gofaster/internal/shared/database"
"gofaster/internal/shared/middleware"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
@ -13,6 +15,7 @@ import ( @@ -13,6 +15,7 @@ import (
type AuthModule struct {
logger *zap.Logger
db *gorm.DB
}
func init() {
@ -25,6 +28,7 @@ func (m *AuthModule) Name() string { @@ -25,6 +28,7 @@ func (m *AuthModule) Name() string {
func (m *AuthModule) Init(config *config.Config, logger *zap.Logger, db *gorm.DB, redis *database.RedisClient) error {
m.logger = logger
m.db = db
// 运行数据库迁移
if err := migration.RunMigrations(db); err != nil {
@ -37,8 +41,16 @@ func (m *AuthModule) Init(config *config.Config, logger *zap.Logger, db *gorm.DB @@ -37,8 +41,16 @@ func (m *AuthModule) Init(config *config.Config, logger *zap.Logger, db *gorm.DB
}
func (m *AuthModule) RegisterRoutes(router *gin.RouterGroup) {
// 这里需要从其他地方获取数据库连接
// 暂时跳过路由注册,在Init阶段处理
if m.db == nil {
m.logger.Error("Database connection not available for auth routes")
return
}
// 注册认证路由
routes.RegisterAuthRoutes(router, m.db, middleware.JWTConfig{
SecretKey: "your-secret-key", // 这里应该从配置中获取
Issuer: "gofaster",
})
}
func (m *AuthModule) Cleanup() {

17
gofaster/backend/internal/auth/service/auth_service.go

@ -276,9 +276,20 @@ func (s *authService) generateRandomCaptcha(length int) string { @@ -276,9 +276,20 @@ func (s *authService) generateRandomCaptcha(length int) string {
// generateCaptchaImage 生成验证码图片(简化版本,返回Base64编码)
func (s *authService) generateCaptchaImage(text string) string {
// 这里应该使用图片生成库生成实际的验证码图片
// 目前返回一个占位符
return fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString([]byte(text)))
// 创建一个简单的文本图片作为验证码
// 使用HTML5 Canvas风格的文本渲染
width := 120
height := 40
// 创建一个简单的SVG图片,包含验证码文本
svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
<rect width="%d" height="%d" fill="#f0f0f0"/>
<text x="%d" y="%d" font-family="Arial, sans-serif" font-size="24" font-weight="bold"
fill="#333" text-anchor="middle" dominant-baseline="middle">%s</text>
</svg>`, width, height, width, height, width/2, height/2, text)
// 将SVG转换为Base64
return fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString([]byte(svg)))
}
// GetClientIP 获取客户端IP地址

63
gofaster/backend/logs/app.log

@ -1,63 +0,0 @@ @@ -1,63 +0,0 @@
{"level":"INFO","ts":"2025-08-05T13:45:22.151+0800","caller":"backend/main.go:61","msg":"Logger initialized","level":"debug","path":"./logs/app.log"}
{"level":"INFO","ts":"2025-08-05T13:47:01.441+0800","caller":"backend/main.go:61","msg":"Logger initialized","level":"debug","path":"./logs/app.log"}
{"level":"INFO","ts":"2025-08-05T13:48:39.815+0800","caller":"logger/logger.go:47","msg":"Logger initialized"}
{"level":"INFO","ts":"2025-08-05T13:48:39.816+0800","caller":"backend/main.go:61","msg":"Logger initialized","level":"debug","path":"./logs/app.log"}
{"level":"INFO","ts":"2025-08-05T13:58:09.521+0800","caller":"logger/logger.go:47","msg":"Logger initialized"}
{"level":"INFO","ts":"2025-08-05T13:58:09.571+0800","caller":"backend/main.go:68","msg":"Logger initialized","level":"debug","path":"./logs/app.log"}
{"level":"INFO","ts":"2025-08-05T14:02:07.899+0800","caller":"logger/logger.go:47","msg":"Logger initialized"}
{"level":"INFO","ts":"2025-08-05T14:02:07.958+0800","caller":"backend/main.go:69","msg":"Logger initialized","level":"debug","path":"./logs/app.log"}
{"level":"INFO","ts":"2025-08-05T14:08:25.003+0800","caller":"logger/logger.go:47","msg":"Logger initialized"}
{"level":"INFO","ts":"2025-08-05T14:08:25.040+0800","caller":"backend/main.go:65","msg":"Logger initialized","level":"debug","path":"./logs/app.log"}
{"level":"INFO","ts":"2025-08-05T14:12:48.479+0800","caller":"logger/logger.go:47","msg":"Logger initialized"}
{"level":"INFO","ts":"2025-08-05T14:12:48.521+0800","caller":"backend/main.go:65","msg":"Logger initialized","level":"debug","path":"./logs/app.log"}
{"level":"INFO","ts":"2025-08-05T14:14:53.222+0800","caller":"logger/logger.go:51","msg":"Logger initialized"}
{"level":"INFO","ts":"2025-08-05T14:14:53.236+0800","caller":"backend/main.go:65","msg":"Logger initialized","level":"debug","path":"./logs/app.log"}
{"level":"INFO","ts":"2025-08-05T14:22:08.260+0800","caller":"backend/main.go:65","msg":"Logger initialized","level":"debug","path":"./logs/app.log"}
test log entry
{"level":"info","ts":1754375400.4735792,"caller":"backend/main.go:65","msg":"Logger initialized","level":"debug","path":"./logs/app.log"}
test log entry
{"level":"info","ts":1754375473.419135,"caller":"backend/main.go:65","msg":"Logger initialized"}
test log entry
{"level":"info","ts":1754375492.219314,"caller":"backend/main.go:63","msg":"Logger initialized"}
{"level":"info","ts":1754375492.2198336,"caller":"backend/main.go:66","msg":"Logger initialized"}
test log entry
test log entry
test log entry
{"level":"info","ts":1754375654.4087517,"caller":"backend/main.go:66","msg":"test log entry"}
test log entry
{"level":"info","ts":1754375919.7963736,"caller":"backend/main.go:66","msg":"test log entry"}
{"level":"info","ts":1754376073.0591264,"caller":"backend/main.go:73","msg":"test log entry"}
{"level":"info","ts":1754376073.0844045,"caller":"backend/main.go:79","msg":"test log entry"}
{"level":"info","ts":1754376169.1850493,"caller":"backend/main.go:73","msg":"test log entry"}
{"level":"info","ts":1754376169.1991498,"caller":"backend/main.go:78","msg":"test log entry"}
{"level":"info","ts":1754376316.8622355,"caller":"backend/main.go:73","msg":"test log entry"}
{"level":"info","ts":1754376316.8788002,"caller":"backend/main.go:77","msg":"Logger Synced"}
{"level":"info","ts":1754376417.6815078,"caller":"backend/main.go:65","msg":"Logger initialized"}
{"level":"info","ts":1754376473.3197138,"caller":"backend/main.go:65","msg":"Logger initialized"}
{"level":"info","ts":1754376473.7748687,"caller":"backend/main.go:78","msg":"Database connected and migrated successfully"}
{"level":"info","ts":1754376725.2128773,"caller":"backend/main.go:65","msg":"Logger initialized"}
{"level":"info","ts":1754376725.7027845,"caller":"backend/main.go:78","msg":"Database connected and migrated successfully"}
{"level":"info","ts":1754376725.7028782,"caller":"database/redis_client.go:23","msg":"Initializing Redis client","host":"localhost","port":"6379","db":0,"has_password":false}
{"level":"warn","ts":1754376725.7784379,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":1,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"warn","ts":1754376726.8704906,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":2,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"warn","ts":1754376727.9956472,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":3,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"fatal","ts":1754376728.997498,"caller":"database/redis_client.go:56","msg":"Failed to connect to Redis after retries","retries":3,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it.","stacktrace":"gofaster/internal/shared/pkg/database.NewRedisClient\n\tD:/aigc/manta/gofaster/backend/internal/shared/pkg/database/redis_client.go:56\nmain.main\n\tD:/aigc/manta/gofaster/backend/main.go:81\nruntime.main\n\tD:/Go/src/runtime/proc.go:283"}
{"level":"info","ts":1754376840.0660605,"caller":"backend/main.go:65","msg":"Logger initialized"}
{"level":"info","ts":1754376840.3610125,"caller":"backend/main.go:78","msg":"Database connected and migrated successfully"}
{"level":"info","ts":1754376840.3615417,"caller":"database/redis_client.go:23","msg":"Initializing Redis client","host":"localhost","port":"6379","db":0,"has_password":false}
{"level":"warn","ts":1754376840.4347446,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":1,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"warn","ts":1754376841.5154507,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":2,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"warn","ts":1754376842.636067,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":3,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"fatal","ts":1754376843.637513,"caller":"database/redis_client.go:56","msg":"Failed to connect to Redis after retries","retries":3,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it.","stacktrace":"gofaster/internal/shared/pkg/database.NewRedisClient\n\tD:/aigc/manta/gofaster/backend/internal/shared/pkg/database/redis_client.go:56\nmain.main\n\tD:/aigc/manta/gofaster/backend/main.go:81\nruntime.main\n\tD:/Go/src/runtime/proc.go:283"}
{"level":"info","ts":1754376853.4036381,"caller":"backend/main.go:65","msg":"Logger initialized"}
{"level":"info","ts":1754376853.681494,"caller":"backend/main.go:78","msg":"Database connected and migrated successfully"}
{"level":"info","ts":1754376853.6816995,"caller":"database/redis_client.go:23","msg":"Initializing Redis client","host":"localhost","port":"6379","db":0,"has_password":false}
{"level":"warn","ts":1754376853.755178,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":1,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"warn","ts":1754376854.8357182,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":2,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"warn","ts":1754376855.9569952,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":3,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"fatal","ts":1754376856.9581835,"caller":"database/redis_client.go:56","msg":"Failed to connect to Redis after retries","retries":3,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it.","stacktrace":"gofaster/internal/shared/pkg/database.NewRedisClient\n\tD:/aigc/manta/gofaster/backend/internal/shared/pkg/database/redis_client.go:56\nmain.main\n\tD:/aigc/manta/gofaster/backend/main.go:81\nruntime.main\n\tD:/Go/src/runtime/proc.go:283"}
{"level":"info","ts":1754381540.3083673,"caller":"database/redis_client.go:23","msg":"Initializing Redis client","host":"localhost","port":"6379","db":0,"has_password":false}
{"level":"warn","ts":1754381540.3953142,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":1,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"warn","ts":1754381541.4798021,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":2,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"warn","ts":1754381542.6024618,"caller":"database/redis_client.go:45","msg":"Redis connection attempt failed","attempt":3,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it."}
{"level":"fatal","ts":1754381543.60342,"caller":"database/redis_client.go:56","msg":"Failed to connect to Redis after retries","retries":3,"error":"dial tcp [::1]:6379: connectex: No connection could be made because the target machine actively refused it.","stacktrace":"gofaster/internal/shared/database.NewRedisClient\n\tD:/aigc/manta/gofaster/backend/internal/shared/database/redis_client.go:56\nmain.main\n\tD:/aigc/manta/gofaster/backend/main.go:59\nruntime.main\n\tD:/Go/src/runtime/proc.go:283"}
Loading…
Cancel
Save