优化项目bug

This commit is contained in:
xuqiuyun
2025-09-16 16:07:32 +08:00
parent 89bc6e8ce2
commit b67f22fd79
22 changed files with 1650 additions and 598 deletions

View File

@@ -58,72 +58,173 @@
## 开发最佳实践
### 前端开发实践
1. **组件化开发**: 遵循原子设计理念将UI拆分为可复用的组件定义清晰的组件接口和Props/Emits规范
1. **组件化开发**: 遵循原子设计理念将UI拆分为可复用的组件定义清晰的组件接口和Props/Emits规范所有数据通过API接口动态获取
```vue
<!-- 原子组件示例 -->
<!-- 动态数据组件示例 -->
<template>
<el-button
:type="type"
:size="size"
:loading="loading"
@click="onClick"
@click="handleClick"
>
<slot></slot>
<slot>{{ buttonText }}</slot>
</el-button>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
import { defineProps, defineEmits, ref, onMounted } from 'vue'
import { fetchButtonConfig } from '@/api/button'
const props = defineProps<{
type?: 'primary' | 'success' | 'warning' | 'danger'
size?: 'small' | 'medium' | 'large'
loading?: boolean
buttonId?: string
}>()
const emit = defineEmits<{
(e: 'click'): void
(e: 'click', data: any): void
}>()
const onClick = () => {
const buttonText = ref('')
const buttonConfig = ref<any>(null)
onMounted(async () => {
if (props.buttonId) {
try {
const response = await fetchButtonConfig(props.buttonId)
if (response.success) {
buttonConfig.value = response.data
buttonText.value = response.data.text || '按钮'
}
} catch (error) {
console.error('获取按钮配置失败:', error)
}
}
})
const handleClick = async () => {
if (!props.loading) {
emit('click')
emit('click', buttonConfig.value)
}
}
</script>
```
2. **状态管理最佳实践**: 使用Pinia管理全局状态遵循单一数据源原则,避免状态冗余,实现状态持久化和模块化管理
2. **状态管理最佳实践**: 使用Pinia管理全局状态所有状态数据通过API接口动态获取避免硬编码
```typescript
// Pinia Store示例
// 动态数据Pinia Store示例
import { defineStore } from 'pinia'
import { fetchUserInfo, login, logout } from '@/api/auth'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null as UserInfo | null,
token: localStorage.getItem('token') || ''
token: localStorage.getItem('token') || '',
permissions: [] as string[],
menus: [] as MenuItem[]
}),
getters: {
isLoggedIn: (state) => !!state.token
isLoggedIn: (state) => !!state.token,
hasPermission: (state) => (permission: string) =>
state.permissions.includes(permission)
},
actions: {
setToken(token: string) {
this.token = token
localStorage.setItem('token', token)
async login(credentials: LoginCredentials) {
const response = await login(credentials)
if (response.success) {
this.token = response.data.token
localStorage.setItem('token', this.token)
await this.loadUserData()
}
return response
},
setUserInfo(userInfo: UserInfo) {
this.userInfo = userInfo
async loadUserData() {
try {
const [userResponse, permResponse, menuResponse] = await Promise.all([
fetchUserInfo(),
fetchPermissions(),
fetchMenus()
])
if (userResponse.success) this.userInfo = userResponse.data
if (permResponse.success) this.permissions = permResponse.data
if (menuResponse.success) this.menus = menuResponse.data
} catch (error) {
console.error('加载用户数据失败:', error)
}
},
logout() {
async logout() {
await logout()
this.token = ''
this.userInfo = null
this.permissions = []
this.menus = []
localStorage.removeItem('token')
}
}
})
```
3. **代码规范**: 严格遵循ESLint/Prettier规范使用TypeScript进行类型定义提高代码可维护性配置EditorConfig保持团队代码风格一致
3. **统一API调用规范**: 使用fetch方法进行所有API调用统一错误处理和响应格式
```typescript
// API工具函数
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api'
interface ApiResponse<T = any> {
code: number
message: string
data: T
success: boolean
timestamp: string
}
async function fetchApi<T = any>(
url: string,
options: RequestInit = {}
): Promise<ApiResponse<T>> {
const defaultOptions: RequestInit = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
...options
}
try {
const response = await fetch(`${API_BASE_URL}${url}`, defaultOptions)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data: ApiResponse<T> = await response.json()
if (!data.success) {
throw new Error(data.message || 'API请求失败')
}
return data
} catch (error) {
console.error('API调用失败:', error)
throw error
}
}
// 具体API调用示例
export const fetchUsers = (params?: any) =>
fetchApi<User[]>('/v1/users', {
method: 'GET',
params
})
export const createUser = (userData: UserCreateDto) =>
fetchApi<User>('/v1/users', {
method: 'POST',
body: JSON.stringify(userData)
})
```
4. **性能优化技巧**:
- 使用虚拟滚动如vue-virtual-scroller处理大数据渲染减少DOM节点数量
@@ -134,11 +235,11 @@
- 避免频繁DOM操作使用虚拟DOM diff算法优势
### 后端开发实践
1. **分层架构实现**: 严格遵循Controller-Service-Repository分层结构职责清晰,实现关注点分离
1. **分层架构实现**: 严格遵循Controller-Service-Repository分层结构所有数据从数据库动态获取,避免硬编码
```java
// Spring Boot分层示例
// Spring Boot动态数据分层示例
@RestController
@RequestMapping("/api/users")
@RequestMapping("/api/v1/users")
public class UserController {
private final UserService userService;
@@ -148,45 +249,113 @@
}
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUserById(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserById(id));
public ResponseEntity<ApiResponse<UserDTO>> getUserById(@PathVariable Long id) {
UserDTO user = userService.getUserById(id);
return ResponseEntity.ok(ApiResponse.success(user));
}
@GetMapping
public ResponseEntity<ApiResponse<PageResult<UserDTO>>> getUsers(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword) {
PageResult<UserDTO> users = userService.getUsers(page, size, keyword);
return ResponseEntity.ok(ApiResponse.success(users));
}
}
@Service
@Transactional
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final PasswordEncoder passwordEncoder;
public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder) {
public UserServiceImpl(UserRepository userRepository,
RoleRepository roleRepository,
PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDTO getUserById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
return convertToDTO(user);
.orElseThrow(() -> new ResourceNotFoundException("用户不存在ID: " + id));
// 动态获取用户角色信息
List<Role> roles = roleRepository.findByUserId(id);
return UserMapper.INSTANCE.toDTO(user, roles);
}
private UserDTO convertToDTO(User user) {
// 转换逻辑
@Override
public PageResult<UserDTO> getUsers(int page, int size, String keyword) {
Pageable pageable = PageRequest.of(page - 1, size, Sort.by("createTime").descending());
Page<User> userPage;
if (StringUtils.hasText(keyword)) {
userPage = userRepository.findByKeyword(keyword, pageable);
} else {
userPage = userRepository.findAll(pageable);
}
// 批量获取用户角色信息
List<Long> userIds = userPage.getContent().stream()
.map(User::getId)
.collect(Collectors.toList());
Map<Long, List<Role>> userRolesMap = roleRepository.findByUserIds(userIds);
List<UserDTO> userDTOs = userPage.getContent().stream()
.map(user -> UserMapper.INSTANCE.toDTO(user, userRolesMap.get(user.getId())))
.collect(Collectors.toList());
return new PageResult<>(userDTOs, userPage.getTotalElements());
}
}
```
```javascript
// Node.js (Express)分层示例
// Node.js (Express)动态数据分层示例
const express = require('express');
const router = express.Router();
const userService = require('../services/user.service');
const { validateRequest } = require('../middleware/validation');
const { userQuerySchema } = require('../schemas/user.schema');
// GET /api/users/:id
// GET /api/v1/users/:id
router.get('/:id', async (req, res, next) => {
try {
const user = await userService.getUserById(req.params.id);
res.json(user);
res.json({
code: 200,
message: 'Success',
data: user,
success: true,
timestamp: new Date().toISOString()
});
} catch (error) {
next(error);
}
});
// GET /api/v1/users
router.get('/', validateRequest(userQuerySchema), async (req, res, next) => {
try {
const { page = 1, pageSize = 10, keyword } = req.query;
const users = await userService.getUsers({
page: parseInt(page),
pageSize: parseInt(pageSize),
keyword
});
res.json({
code: 200,
message: 'Success',
data: users,
success: true,
timestamp: new Date().toISOString()
});
} catch (error) {
next(error);
}
@@ -194,43 +363,411 @@
module.exports = router;
// user.service.js
// user.service.js - 动态数据服务层
const userRepository = require('../repositories/user.repository');
const roleRepository = require('../repositories/role.repository');
async function getUserById(id) {
const user = await userRepository.findById(id);
if (!user) {
throw new Error(`User not found with id: ${id}`);
throw new Error(`用户不存在ID: ${id}`);
}
return mapUserToDTO(user);
// 动态获取用户角色信息
const roles = await roleRepository.findByUserId(id);
return mapUserToDTO(user, roles);
}
function mapUserToDTO(user) {
// 转换逻辑
async function getUsers({ page, pageSize, keyword }) {
const offset = (page - 1) * pageSize;
let users, total;
if (keyword) {
[users, total] = await Promise.all([
userRepository.findByKeyword(keyword, pageSize, offset),
userRepository.countByKeyword(keyword)
]);
} else {
[users, total] = await Promise.all([
userRepository.findAll(pageSize, offset),
userRepository.count()
]);
}
// 批量获取用户角色信息
const userIds = users.map(user => user.id);
const userRolesMap = await roleRepository.findByUserIds(userIds);
const userDTOs = users.map(user =>
mapUserToDTO(user, userRolesMap[user.id] || [])
);
return {
items: userDTOs,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
};
}
function mapUserToDTO(user, roles) {
return {
id: user.id,
username: user.username,
email: user.email,
avatar: user.avatar,
status: user.status,
roles: roles.map(role => ({
id: role.id,
name: role.name,
code: role.code
})),
createTime: user.createTime,
updateTime: user.updateTime
};
}
```
2. **数据库优化**: 合理设计索引优化SQL查询避免SELECT *、使用JOIN替代子查询使用连接池管理数据库连接实现读写分离
3. **异常处理**: 统一异常处理机制,定义清晰的错误码和错误信息,使用全局异常处理器捕获和处理异常
4. **接口设计**: RESTful风格API版本化管理如/api/v1/users参数校验使用JSR-380/express-validator返回统一的数据结构
```json
// 统一响应格式
{
"code": 200,
"message": "Success",
"data": { /* 具体数据 */ },
"timestamp": "2023-07-01T12:00:00Z"
2. **统一响应格式**: 所有API返回统一格式的响应包含状态码、消息、数据和时间戳
```typescript
// 统一响应格式接口定义
interface ApiResponse<T = any> {
code: number; // 状态码
message: string; // 消息
data: T; // 数据
success: boolean; // 是否成功
timestamp: string; // 时间戳
}
// 分页响应格式
interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}
// 成功响应工具函数
function successResponse<T>(data: T, message: string = 'Success'): ApiResponse<T> {
return {
code: 200,
message,
data,
success: true,
timestamp: new Date().toISOString()
};
}
// 错误响应工具函数
function errorResponse(code: number, message: string): ApiResponse<null> {
return {
code,
message,
data: null,
success: false,
timestamp: new Date().toISOString()
};
}
```
3. **数据库优化**: 合理设计索引优化SQL查询避免SELECT *、使用JOIN替代子查询使用连接池管理数据库连接实现读写分离
4. **异常处理**: 统一异常处理机制,定义清晰的错误码和错误信息,使用全局异常处理器捕获和处理异常
5. **安全编码**: 防止SQL注入使用参数化查询、XSS攻击输入过滤、输出编码、CSRF攻击使用CSRF令牌等安全问题敏感数据如密码加密存储
### 统一API调用规范
1. **前端API调用标准**: 使用统一的fetch封装避免硬编码URL和静态数据
```typescript
// api-client.ts - 统一API客户端
const BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
success: boolean;
timestamp: string;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}
class ApiClient {
private async request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<ApiResponse<T>> {
const url = `${BASE_URL}${endpoint}`;
const config: RequestInit = {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
credentials: 'include',
...options,
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: ApiResponse<T> = await response.json();
if (!data.success) {
throw new Error(data.message || 'API request failed');
}
return data;
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
async get<T>(endpoint: string, params?: Record<string, any>): Promise<ApiResponse<T>> {
const queryString = params ? new URLSearchParams(params).toString() : '';
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
return this.request<T>(url, { method: 'GET' });
}
async post<T>(endpoint: string, data?: any): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: 'POST',
body: data ? JSON.stringify(data) : undefined,
});
}
async put<T>(endpoint: string, data?: any): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: 'PUT',
body: data ? JSON.stringify(data) : undefined,
});
}
async delete<T>(endpoint: string): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, { method: 'DELETE' });
}
}
export const apiClient = new ApiClient();
// 用户服务API
export const userApi = {
// 获取用户列表
getUsers: (params: {
page?: number;
pageSize?: number;
keyword?: string;
}) => apiClient.get<PaginatedResponse<User>>('/v1/users', params),
// 获取用户详情
getUserById: (id: number) => apiClient.get<User>(`/v1/users/${id}`),
// 创建用户
createUser: (userData: CreateUserRequest) =>
apiClient.post<User>('/v1/users', userData),
// 更新用户
updateUser: (id: number, userData: UpdateUserRequest) =>
apiClient.put<User>(`/v1/users/${id}`, userData),
// 删除用户
deleteUser: (id: number) => apiClient.delete(`/v1/users/${id}`),
};
// 在Vue组件中使用
import { userApi } from '@/services/api';
const loadUsers = async () => {
try {
const response = await userApi.getUsers({
page: currentPage.value,
pageSize: pageSize.value,
keyword: searchKeyword.value
});
if (response.success) {
users.value = response.data.items;
total.value = response.data.total;
}
} catch (error) {
console.error('Failed to load users:', error);
message.error('加载用户列表失败');
}
};
```
2. **后端统一响应格式**: 所有接口返回标准化的响应结构
```java
// Spring Boot统一响应格式
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private boolean success;
private String timestamp;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "Success", data, true, LocalDateTime.now().toString());
}
public static <T> ApiResponse<T> success(T data, String message) {
return new ApiResponse<>(200, message, data, true, LocalDateTime.now().toString());
}
public static ApiResponse<?> error(int code, String message) {
return new ApiResponse<>(code, message, null, false, LocalDateTime.now().toString());
}
public static ApiResponse<?> error(String message) {
return new ApiResponse<>(500, message, null, false, LocalDateTime.now().toString());
}
}
// 分页响应格式
@Data
@AllArgsConstructor
public class PageResult<T> {
private List<T> items;
private long total;
private int page;
private int pageSize;
private int totalPages;
public PageResult(List<T> items, long total) {
this.items = items;
this.total = total;
this.page = 1;
this.pageSize = items.size();
this.totalPages = (int) Math.ceil((double) total / pageSize);
}
public PageResult(List<T> items, long total, int page, int pageSize) {
this.items = items;
this.total = total;
this.page = page;
this.pageSize = pageSize;
this.totalPages = (int) Math.ceil((double) total / pageSize);
}
}
```
```javascript
// Node.js统一响应中间件
function apiResponseMiddleware(req, res, next) {
res.apiSuccess = function(data, message = 'Success') {
this.json({
code: 200,
message,
data,
success: true,
timestamp: new Date().toISOString()
});
};
res.apiError = function(code, message, data = null) {
this.status(code).json({
code,
message,
data,
success: false,
timestamp: new Date().toISOString()
});
};
next();
}
// 在控制器中使用
router.get('/v1/users/:id', async (req, res) => {
try {
const user = await userService.getUserById(req.params.id);
res.apiSuccess(user);
} catch (error) {
if (error.message.includes('不存在')) {
res.apiError(404, error.message);
} else {
res.apiError(500, '服务器内部错误');
}
}
});
```
3. **数据验证和错误处理**: 统一的参数验证和异常处理机制
```typescript
// 前端数据验证
interface CreateUserRequest {
username: string;
email: string;
password: string;
roleIds: number[];
}
const validateUserData = (data: CreateUserRequest): string[] => {
const errors: string[] = [];
if (!data.username || data.username.length < 3) {
errors.push('用户名至少3个字符');
}
if (!data.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
errors.push('请输入有效的邮箱地址');
}
if (!data.password || data.password.length < 6) {
errors.push('密码至少6个字符');
}
if (!data.roleIds || data.roleIds.length === 0) {
errors.push('请选择至少一个角色');
}
return errors;
};
// 在组件中使用验证
const handleCreateUser = async () => {
const errors = validateUserData(formData);
if (errors.length > 0) {
message.error(errors.join(''));
return;
}
try {
const response = await userApi.createUser(formData);
if (response.success) {
message.success('用户创建成功');
loadUsers(); // 重新加载数据
}
} catch (error) {
message.error('创建用户失败');
}
};
```
### 通用开发原则
1. **单一职责原则**: 每个函数、类只负责一个明确的功能,提高代码可维护性
2. **DRY原则(Don't Repeat Yourself)**: 避免代码重复,提高代码复用性,抽取公共函数和组件
3. **KISS原则(Keep It Simple, Stupid)**: 保持代码简洁明了,避免过度设计,优先选择简单的解决方案
4. **YAGNI原则(You Aren't Gonna Need It)**: 只实现当前需要的功能,避免过度设计和功能膨胀
5. **代码注释**: 为复杂逻辑和关键算法添加清晰的注释提高代码可读性遵循JSDoc/Javadoc规范
6. **SOLID原则**: 遵循单一职责、开闭原则、里氏替换、接口隔离、依赖倒置等设计原则
1. **SOLID原则**: 单一职责、开闭原则、里氏替换、接口隔离、依赖倒置
2. **DRY原则**: 避免重复代码,提取公共方法和组件
3. **KISS原则**: 保持简单和直接,避免过度设计
4. **YAGNI原则**: 不要实现你不需要的功能,避免过度工程化
5. **代码复用**: 创建可复用的组件、工具函数和库
6. **渐进式增强**: 从基础功能开始,逐步添加高级功能
7. **防御性编程**: 对输入进行验证,处理异常情况
8. **性能意识**: 关注代码性能,避免不必要的计算和内存消耗
9. **可测试性**: 编写可测试的代码,使用依赖注入和接口
10. **文档化**: 编写清晰的注释和文档,便于维护和协作
## 协作与沟通