Compare commits

...

7 Commits

Author SHA1 Message Date
yourname
d2ab3d7bba feat(auth): 添加默认管理员账户创建功能
- 实现ensureAdminExists方法,确保admin用户存在
- 登录时检查管理员账户,不存在则自动创建默认管理员
- 添加管理员默认账号密码配置(admin/admin123)

♻️ refactor(auth): 优化日志记录方式

- 引入debug模块替代console.error进行日志记录
- 创建专用logger对象区分不同级别日志
- 替换登录错误处理中的console.error为logger.error

🔧 chore(auth): 添加必要的类型导入和常量定义

- 导入DisabledStatus类型用于用户状态管理
- 定义ADMIN_USERNAME和ADMIN_PASSWORD常量
2025-07-10 23:40:45 +00:00
yourname
cf0509ccd3 📝 docs(logging): update logging examples to show direct usage pattern
- 修改前后端日志示例,移除集中式logger对象
- 展示在使用处直接引入debug并定义命名空间的方式
- 添加具体的日志使用示例代码
- 简化日志使用流程,避免额外的工具类依赖
2025-07-10 23:37:58 +00:00
yourname
cd86729115 优化 header 2025-06-27 04:30:04 +00:00
yourname
527b1b7837 优化 登录 2025-06-27 04:08:21 +00:00
yourname
af567310df u 2025-06-27 03:58:31 +00:00
yourname
e0c3a26ca1 统一用 import { z } from '@hono/zod-openapi' 2025-06-27 03:51:26 +00:00
yourname
6693653c94 去掉vite-plugin-i18next-loader 2025-06-27 03:47:29 +00:00
23 changed files with 146 additions and 1825 deletions

View File

@@ -29,28 +29,34 @@ k8s:deployment:create # K8S-部署-创建
### 前端示例
```typescript
// src/client/utils/logger.ts
// 在需要使用日志的文件中直接引入debug
import debug from 'debug';
export const logger = {
error: debug('frontend:error'),
api: debug('frontend:api'),
auth: debug('frontend:auth'),
ui: debug('frontend:ui')
};
// 按需定义命名空间
const errorLogger = debug('frontend:error');
const apiLogger = debug('frontend:api');
const authLogger = debug('frontend:auth');
const uiLogger = debug('frontend:ui');
// 使用示例
errorLogger('用户登录失败: %s', error.message);
apiLogger('API请求: %s', url);
```
### 后端示例
```typescript
// src/server/utils/logger.ts
// 在需要使用日志的文件中直接引入debug
import debug from 'debug';
export const logger = {
error: debug('backend:error'),
api: debug('backend:api'),
db: debug('backend:db'),
middleware: debug('backend:middleware')
};
// 按需定义命名空间
const errorLogger = debug('backend:error');
const apiLogger = debug('backend:api');
const dbLogger = debug('backend:db');
const middlewareLogger = debug('backend:middleware');
// 使用示例
errorLogger('数据库连接失败: %s', error.message);
dbLogger('查询执行: %s', sql);
```
## 5. 最佳实践

View File

@@ -4,7 +4,7 @@
```typescript
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { z } from 'zod';
import { z } from '@hono/zod-openapi';
@Entity('table_name') // 使用小写下划线命名表名
export class EntityName {

View File

@@ -8,6 +8,7 @@
"start": "export NODE_ENV='production' && node dist-server/index.js"
},
"dependencies": {
"@ant-design/icons": "^6.0.0",
"@emotion/react": "^11.14.0",
"@heroicons/react": "^2.2.0",
"@hono/node-server": "^1.14.3",

1767
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,14 @@
import React from 'react';
import { useRouteError, useNavigate } from 'react-router';
import { Alert, Button } from 'antd';
import { useTheme } from '../hooks/ThemeProvider';
export const ErrorPage = () => {
const navigate = useNavigate();
const { isDark } = useTheme();
const error = useRouteError() as any;
const errorMessage = error?.statusText || error?.message || '未知错误';
return (
<div className="flex flex-col items-center justify-center flex-grow p-4"
style={{ color: isDark ? '#fff' : 'inherit' }}
>
<div className="max-w-3xl w-full">
<h1 className="text-2xl font-bold mb-4"></h1>

View File

@@ -1,16 +1,12 @@
import React from 'react';
import { useNavigate } from 'react-router';
import { Button } from 'antd';
import { useTheme } from '../hooks/ThemeProvider';
export const NotFoundPage = () => {
const navigate = useNavigate();
const { isDark } = useTheme();
return (
<div className="flex flex-col items-center justify-center flex-grow p-4"
style={{ color: isDark ? '#fff' : 'inherit' }}
>
<div className="flex flex-col items-center justify-center flex-grow p-4">
<div className="max-w-3xl w-full">
<h1 className="text-2xl font-bold mb-4">404 - </h1>
<p className="mb-6 text-gray-600 dark:text-gray-300">

View File

@@ -114,6 +114,7 @@ export const MainLayout = () => {
collapsed={collapsed}
width={240}
className="custom-sider"
theme='light'
style={{
overflow: 'auto',
height: '100vh',
@@ -122,6 +123,8 @@ export const MainLayout = () => {
top: 0,
bottom: 0,
zIndex: 100,
transition: 'all 0.2s ease',
boxShadow: '2px 0 8px 0 rgba(29, 35, 41, 0.05)',
}}
>
<div className="p-4">
@@ -156,12 +159,9 @@ export const MainLayout = () => {
</Sider>
<Layout style={{ marginLeft: collapsed ? 80 : 240, transition: 'margin-left 0.2s' }}>
<Header className="p-0 flex justify-between items-center"
<div className="sticky top-0 z-50 bg-white shadow-sm transition-all duration-200 h-16 flex items-center justify-between pl-2"
style={{
position: 'sticky',
top: 0,
zIndex: 99,
boxShadow: '0 1px 4px rgba(0,21,41,0.08)',
boxShadow: '0 1px 4px rgba(0,21,41,0.08)'
}}
>
<Button
@@ -191,10 +191,10 @@ export const MainLayout = () => {
</Space>
</Dropdown>
</Space>
</Header>
</div>
<Content className="m-6" style={{ overflow: 'initial' }}>
<div className="site-layout-content p-6 rounded-lg">
<Content className="m-6" style={{ overflow: 'initial', transition: 'all 0.2s ease' }}>
<div className="site-layout-content p-6 rounded-lg bg-white shadow-sm">
<Outlet />
</div>

View File

@@ -4,7 +4,7 @@ import {
Input,
Button,
Card,
message,
App,
} from 'antd';
import {
UserOutlined,
@@ -17,6 +17,7 @@ import {
// 登录页面
export const LoginPage = () => {
const { message } = App.useApp();
const { login } = useAuth();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
@@ -46,7 +47,7 @@ export const LoginPage = () => {
// 登录成功后跳转到管理后台首页
navigate('/admin/dashboard');
} catch (error: any) {
message.error(error.response?.data?.error || '登录失败');
message.error(error instanceof Error ? error.message : '登录失败');
} finally {
setLoading(false);
}

View File

@@ -1,4 +1,3 @@
import { Link } from 'react-router-dom'
import { createRoot } from 'react-dom/client'
import { getGlobalConfig } from '../utils/utils'
@@ -22,20 +21,20 @@ const Home = () => {
{/* 管理入口按钮 */}
<div className="space-y-4">
<Link
to="/admin"
<a
href="/admin"
className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-lg font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
</Link>
</a>
{/* 移动端入口按钮 */}
<Link
to="/mobile"
<a
href="/mobile"
className="w-full flex justify-center py-3 px-4 border border-blue-600 rounded-md shadow-sm text-lg font-medium text-blue-600 bg-white hover:bg-blue-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
</Link>
</a>
</div>
</div>

View File

@@ -1,7 +1,7 @@
import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
import { AuthService } from '../../../modules/auth/auth.service'
import { UserService } from '../../../modules/users/user.service'
import { z } from 'zod'
import { z } from '@hono/zod-openapi'
import { ErrorSchema } from '../../../utils/errorHandler'
import { AppDataSource } from '../../../data-source'
import { AuthContext } from '../../../types/context'

View File

@@ -1,5 +1,5 @@
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
import { z } from 'zod'
import { z } from '@hono/zod-openapi'
import { AuthContext } from '@/server/types/context';
import { authMiddleware } from '@/server/middleware/auth.middleware';
import { AppDataSource } from '@/server/data-source';

View File

@@ -1,7 +1,7 @@
import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
import { AuthService } from '../../../modules/auth/auth.service'
import { UserService } from '../../../modules/users/user.service'
import { z } from 'zod'
import { z } from '@hono/zod-openapi'
import { AppDataSource } from '../../../data-source'
import { ErrorSchema } from '../../../utils/errorHandler'
import { AuthContext } from '../../../types/context'

View File

@@ -1,6 +1,6 @@
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
import { UserService } from '@/server/modules/users/user.service';
import { z } from 'zod';
import { z } from '@hono/zod-openapi';
import { authMiddleware } from '@/server/middleware/auth.middleware';
import { ErrorSchema } from '@/server/utils/errorHandler';
import { AppDataSource } from '@/server/data-source';

View File

@@ -1,6 +1,6 @@
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
import { UserService } from '@/server/modules/users/user.service';
import { z } from 'zod';
import { z } from '@hono/zod-openapi';
import { authMiddleware } from '@/server/middleware/auth.middleware';
import { ErrorSchema } from '@/server/utils/errorHandler';
import { AppDataSource } from '@/server/data-source';

View File

@@ -1,6 +1,6 @@
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
import { UserService } from '@/server/modules/users/user.service';
import { z } from 'zod';
import { z } from '@hono/zod-openapi';
import { authMiddleware } from '@/server/middleware/auth.middleware';
import { ErrorSchema } from '@/server/utils/errorHandler';
import { AppDataSource } from '@/server/data-source';

View File

@@ -1,6 +1,6 @@
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
import { UserService } from '../../modules/users/user.service';
import { z } from 'zod';
import { z } from '@hono/zod-openapi';
import { authMiddleware } from '../../middleware/auth.middleware';
import { ErrorSchema } from '../../utils/errorHandler';
import { AppDataSource } from '../../data-source';

View File

@@ -1,6 +1,6 @@
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
import { UserService } from '../../modules/users/user.service';
import { z } from 'zod';
import { z } from '@hono/zod-openapi';
import { authMiddleware } from '../../middleware/auth.middleware';
import { ErrorSchema } from '../../utils/errorHandler';
import { AppDataSource } from '../../data-source';

View File

@@ -17,6 +17,6 @@ export const AppDataSource = new DataSource({
User, Role
],
migrations: [],
synchronize: process.env.DB_SYNCHRONIZE === "true",
synchronize: process.env.DB_SYNCHRONIZE !== "false",
logging: process.env.DB_LOGGING === "true",
});

View File

@@ -1,9 +1,18 @@
import jwt from 'jsonwebtoken';
import { UserService } from '../users/user.service';
import { UserEntity as User } from '../users/user.entity';
import { DisabledStatus } from '@/share/types';
import debug from 'debug';
const logger = {
info: debug('backend:auth:info'),
error: debug('backend:auth:error')
}
const JWT_SECRET = 'your-secret-key'; // 生产环境应使用环境变量
const JWT_EXPIRES_IN = '7d'; // 7天有效期
const ADMIN_USERNAME = 'admin';
const ADMIN_PASSWORD = 'admin123';
export class AuthService {
private userService: UserService;
@@ -12,8 +21,33 @@ export class AuthService {
this.userService = userService;
}
async ensureAdminExists(): Promise<User> {
try {
let admin = await this.userService.getUserByUsername(ADMIN_USERNAME);
if (!admin) {
logger.info('Admin user not found, creating default admin account');
admin = await this.userService.createUser({
username: ADMIN_USERNAME,
password: ADMIN_PASSWORD,
nickname: '系统管理员',
isDisabled: DisabledStatus.ENABLED
});
logger.info('Default admin account created successfully');
}
return admin;
} catch (error) {
logger.error('Failed to ensure admin account exists:', error);
throw error;
}
}
async login(username: string, password: string): Promise<{ token: string; user: User }> {
try {
// 确保admin用户存在
if (username === ADMIN_USERNAME) {
await this.ensureAdminExists();
}
const user = await this.userService.getUserByUsername(username);
if (!user) {
throw new Error('User not found');
@@ -27,7 +61,7 @@ export class AuthService {
const token = this.generateToken(user);
return { token, user };
} catch (error) {
console.error('Login error:', error);
logger.error('Login error:', error);
throw error;
}
}

View File

@@ -1,5 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { z } from 'zod';
import { z } from '@hono/zod-openapi';
export type Permission = string;

View File

@@ -1,5 +1,5 @@
import { Context } from 'hono'
import { z } from 'zod'
import { z } from '@hono/zod-openapi'
import { HTTPException } from 'hono/http-exception'
export const ErrorSchema = z.object({

View File

@@ -1 +1,55 @@
@import 'tailwindcss';
/* 全局滚动条样式 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 响应式断点 */
@media (max-width: 768px) {
.custom-sider {
width: 100% !important;
max-width: 100% !important;
}
.site-layout-content {
padding: 1rem !important;
}
}
/* 全局过渡效果 */
.transition-all {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
/* 内容区域阴影优化 */
.shadow-sm {
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
/* 按钮悬停效果 */
.ant-btn:hover {
transform: translateY(-1px);
}
/* 卡片样式优化 */
.ant-card {
border-radius: 8px;
}

View File

@@ -1,12 +1,8 @@
import reactStack from 'hono-vite-react-stack-node'
import { defineConfig } from 'vite'
import i18nextLoader from 'vite-plugin-i18next-loader'
export default defineConfig({
plugins: [
i18nextLoader({
paths: ['src/client/i18n/locales']
}),
reactStack({
minify: false,
port: 8080