优化项目bug
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
const { CattleBatch, IotCattle, Farm, CattleBatchAnimal, User } = require('../models');
|
||||
const { CattleBatch, IotCattle, Farm, CattleBatchAnimal, User, CattleType, CattleUser, CattlePen } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
@@ -386,6 +386,9 @@ class CattleBatchController {
|
||||
const { page = 1, pageSize = 10 } = req.query;
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
console.log('🔍 开始获取批次牛只数据');
|
||||
console.log('📋 批次信息:', { id, page, pageSize, offset });
|
||||
|
||||
// 检查批次是否存在
|
||||
const batch = await CattleBatch.findByPk(id);
|
||||
if (!batch) {
|
||||
@@ -395,31 +398,127 @@ class CattleBatchController {
|
||||
});
|
||||
}
|
||||
|
||||
// 获取批次中的牛只
|
||||
console.log('✅ 批次存在:', batch.name);
|
||||
|
||||
// 获取批次中的牛只(直接通过batchId字段查询)
|
||||
const { count, rows } = await IotCattle.findAndCountAll({
|
||||
where: { batchId: id },
|
||||
attributes: [
|
||||
'id',
|
||||
'earNumber',
|
||||
'sex',
|
||||
'strain',
|
||||
'varieties',
|
||||
'birthday',
|
||||
'parity',
|
||||
'orgId',
|
||||
'penId'
|
||||
],
|
||||
include: [
|
||||
{
|
||||
model: CattleBatchAnimal,
|
||||
as: 'batchAnimals',
|
||||
where: { batchId: id },
|
||||
attributes: ['id', 'joinDate', 'notes']
|
||||
},
|
||||
{
|
||||
model: Farm,
|
||||
as: 'farm',
|
||||
attributes: ['id', 'name']
|
||||
}
|
||||
],
|
||||
attributes: ['id', 'earNumber', 'sex', 'strain', 'orgId'],
|
||||
limit: parseInt(pageSize),
|
||||
offset: offset,
|
||||
order: [['earNumber', 'ASC']]
|
||||
});
|
||||
|
||||
console.log(`📊 查询结果: 总记录数=${count}, 返回记录数=${rows.length}`);
|
||||
|
||||
// 获取品种和品系映射数据
|
||||
const typeIds = [...new Set(rows.map(cattle => cattle.varieties).filter(id => id))];
|
||||
const strainIds = [...new Set(rows.map(cattle => cattle.strain).filter(id => id))];
|
||||
const penIds = [...new Set(rows.map(cattle => cattle.penId).filter(id => id))];
|
||||
|
||||
const typeNames = {};
|
||||
if (typeIds.length > 0) {
|
||||
const types = await CattleType.findAll({
|
||||
where: { id: typeIds },
|
||||
attributes: ['id', 'name']
|
||||
});
|
||||
types.forEach(type => {
|
||||
typeNames[type.id] = type.name;
|
||||
});
|
||||
}
|
||||
|
||||
const userNames = {};
|
||||
if (strainIds.length > 0) {
|
||||
const users = await CattleUser.findAll({
|
||||
where: { id: strainIds },
|
||||
attributes: ['id', 'name']
|
||||
});
|
||||
users.forEach(user => {
|
||||
userNames[user.id] = user.name;
|
||||
});
|
||||
}
|
||||
|
||||
const penNames = {};
|
||||
if (penIds.length > 0) {
|
||||
const pens = await CattlePen.findAll({
|
||||
where: { id: penIds },
|
||||
attributes: ['id', 'name']
|
||||
});
|
||||
pens.forEach(pen => {
|
||||
penNames[pen.id] = pen.name;
|
||||
});
|
||||
}
|
||||
|
||||
// 转换数据格式,添加计算字段
|
||||
const transformedRows = rows.map(cattle => {
|
||||
// 计算月龄(基于出生日期)
|
||||
let ageInMonths = 0;
|
||||
if (cattle.birthday) {
|
||||
const birthDate = new Date(cattle.birthday * 1000);
|
||||
const now = new Date();
|
||||
ageInMonths = Math.floor((now - birthDate) / (1000 * 60 * 60 * 24 * 30));
|
||||
}
|
||||
|
||||
// 性别转换
|
||||
const genderMap = { 1: '公', 2: '母', 0: '未知' };
|
||||
const gender = genderMap[cattle.sex] || '未知';
|
||||
|
||||
// 品种转换(动态查询)
|
||||
const breed = typeNames[cattle.varieties] || `品种ID:${cattle.varieties}`;
|
||||
|
||||
// 生理阶段判断
|
||||
let physiologicalStage = '未知';
|
||||
if (ageInMonths < 6) {
|
||||
physiologicalStage = '犊牛';
|
||||
} else if (ageInMonths < 12) {
|
||||
physiologicalStage = '育成牛';
|
||||
} else if (ageInMonths < 24) {
|
||||
physiologicalStage = '青年牛';
|
||||
} else if (cattle.sex === 2) {
|
||||
if (cattle.parity > 0) {
|
||||
physiologicalStage = '泌乳牛';
|
||||
} else {
|
||||
physiologicalStage = '后备母牛';
|
||||
}
|
||||
} else {
|
||||
physiologicalStage = '种公牛';
|
||||
}
|
||||
|
||||
return {
|
||||
id: cattle.id,
|
||||
earTag: cattle.earNumber,
|
||||
breed: breed,
|
||||
gender: gender,
|
||||
ageInMonths: ageInMonths,
|
||||
physiologicalStage: physiologicalStage,
|
||||
pen: cattle.penId ? (penNames[cattle.penId] || `栏舍ID:${cattle.penId}`) : '未分配栏舍',
|
||||
farm: cattle.farm
|
||||
};
|
||||
});
|
||||
|
||||
console.log('🔄 转换后的数据示例:', transformedRows.slice(0, 2));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
list: rows,
|
||||
list: transformedRows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { CattlePen, Farm, IotCattle } = require('../models');
|
||||
const { CattlePen, Farm, IotCattle, CattleType, CattleUser } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
@@ -378,7 +378,16 @@ class CattlePenController {
|
||||
// 获取栏舍中的牛只
|
||||
const { count, rows } = await IotCattle.findAndCountAll({
|
||||
where: { penId: id },
|
||||
attributes: ['id', 'earNumber', 'sex', 'strain', 'orgId'],
|
||||
attributes: [
|
||||
'id',
|
||||
'earNumber',
|
||||
'sex',
|
||||
'strain',
|
||||
'varieties',
|
||||
'birthday',
|
||||
'parity',
|
||||
'orgId'
|
||||
],
|
||||
include: [
|
||||
{
|
||||
model: Farm,
|
||||
@@ -391,10 +400,82 @@ class CattlePenController {
|
||||
order: [['earNumber', 'ASC']]
|
||||
});
|
||||
|
||||
// 获取品种和品系映射数据
|
||||
const typeIds = [...new Set(rows.map(cattle => cattle.varieties).filter(id => id))];
|
||||
const strainIds = [...new Set(rows.map(cattle => cattle.strain).filter(id => id))];
|
||||
|
||||
const typeNames = {};
|
||||
if (typeIds.length > 0) {
|
||||
const types = await CattleType.findAll({
|
||||
where: { id: typeIds },
|
||||
attributes: ['id', 'name']
|
||||
});
|
||||
types.forEach(type => {
|
||||
typeNames[type.id] = type.name;
|
||||
});
|
||||
}
|
||||
|
||||
const userNames = {};
|
||||
if (strainIds.length > 0) {
|
||||
const users = await CattleUser.findAll({
|
||||
where: { id: strainIds },
|
||||
attributes: ['id', 'name']
|
||||
});
|
||||
users.forEach(user => {
|
||||
userNames[user.id] = user.name;
|
||||
});
|
||||
}
|
||||
|
||||
// 转换数据格式,添加计算字段
|
||||
const transformedRows = rows.map(cattle => {
|
||||
// 计算月龄(基于出生日期)
|
||||
let ageInMonths = 0;
|
||||
if (cattle.birthday) {
|
||||
const birthDate = new Date(cattle.birthday * 1000); // 假设birthday是Unix时间戳
|
||||
const now = new Date();
|
||||
ageInMonths = Math.floor((now - birthDate) / (1000 * 60 * 60 * 24 * 30));
|
||||
}
|
||||
|
||||
// 性别转换
|
||||
const genderMap = { 1: '公', 2: '母', 0: '未知' };
|
||||
const gender = genderMap[cattle.sex] || '未知';
|
||||
|
||||
// 品种转换(动态查询)
|
||||
const breed = typeNames[cattle.varieties] || `品种ID:${cattle.varieties}`;
|
||||
|
||||
// 生理阶段判断(基于月龄和性别)
|
||||
let physiologicalStage = '未知';
|
||||
if (ageInMonths < 6) {
|
||||
physiologicalStage = '犊牛';
|
||||
} else if (ageInMonths < 12) {
|
||||
physiologicalStage = '育成牛';
|
||||
} else if (ageInMonths < 24) {
|
||||
physiologicalStage = '青年牛';
|
||||
} else if (cattle.sex === 2) { // 母牛
|
||||
if (cattle.parity > 0) {
|
||||
physiologicalStage = '泌乳牛';
|
||||
} else {
|
||||
physiologicalStage = '后备母牛';
|
||||
}
|
||||
} else { // 公牛
|
||||
physiologicalStage = '种公牛';
|
||||
}
|
||||
|
||||
return {
|
||||
id: cattle.id,
|
||||
earTag: cattle.earNumber, // 映射到前端期望的字段名
|
||||
breed: breed,
|
||||
gender: gender,
|
||||
ageInMonths: ageInMonths,
|
||||
physiologicalStage: physiologicalStage,
|
||||
farm: cattle.farm
|
||||
};
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
list: rows,
|
||||
list: transformedRows,
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize)
|
||||
|
||||
@@ -24,15 +24,15 @@ const calculateAgeInMonths = (birthday) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 类别中文映射
|
||||
* 类别中文映射(与前端保持一致)
|
||||
*/
|
||||
const getCategoryName = (cate) => {
|
||||
const categoryMap = {
|
||||
1: '犊牛',
|
||||
2: '繁殖牛',
|
||||
3: '基础母牛',
|
||||
4: '隔离牛',
|
||||
5: '治疗牛',
|
||||
2: '育成母牛',
|
||||
3: '架子牛',
|
||||
4: '青年牛',
|
||||
5: '基础母牛',
|
||||
6: '育肥牛'
|
||||
};
|
||||
return categoryMap[cate] || '未知';
|
||||
@@ -128,10 +128,20 @@ class IotCattleController {
|
||||
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
whereConditions[Op.or] = [
|
||||
{ earNumber: { [Op.like]: `%${search}%` } },
|
||||
{ strain: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
// 尝试将搜索词转换为数字,如果成功则按数字搜索,否则按字符串搜索
|
||||
const searchNumber = parseInt(search);
|
||||
if (!isNaN(searchNumber)) {
|
||||
// 数字搜索:精确匹配耳号
|
||||
whereConditions[Op.or] = [
|
||||
{ earNumber: searchNumber },
|
||||
{ strain: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
} else {
|
||||
// 字符串搜索:模糊匹配
|
||||
whereConditions[Op.or] = [
|
||||
{ strain: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
console.log('=== 搜索条件构建 ===');
|
||||
console.log('搜索关键词:', search);
|
||||
console.log('搜索条件对象:', JSON.stringify(whereConditions, null, 2));
|
||||
@@ -159,16 +169,13 @@ class IotCattleController {
|
||||
console.log('完整查询条件:', JSON.stringify(whereConditions, null, 2));
|
||||
console.log('分页参数:', { offset, limit: pageSize });
|
||||
|
||||
// 先获取总数(严格查询未删除的记录)
|
||||
// 先获取总数
|
||||
console.log('=== 开始数据库查询 ===');
|
||||
console.log('查询时间:', new Date().toISOString());
|
||||
|
||||
const countStartTime = Date.now();
|
||||
const totalCount = await IotCattle.count({
|
||||
where: {
|
||||
...whereConditions,
|
||||
isDelete: 0 // 确保只统计未删除的记录
|
||||
}
|
||||
where: whereConditions
|
||||
});
|
||||
const countEndTime = Date.now();
|
||||
|
||||
@@ -176,13 +183,10 @@ class IotCattleController {
|
||||
console.log('查询耗时:', countEndTime - countStartTime, 'ms');
|
||||
console.log('总记录数:', totalCount);
|
||||
|
||||
// 获取分页数据(严格查询未删除的记录)
|
||||
// 获取分页数据
|
||||
const dataStartTime = Date.now();
|
||||
const rows = await IotCattle.findAll({
|
||||
where: {
|
||||
...whereConditions,
|
||||
isDelete: 0 // 确保只查询未删除的记录
|
||||
},
|
||||
where: whereConditions,
|
||||
attributes: [
|
||||
'id', 'earNumber', 'sex', 'strain', 'varieties', 'cate',
|
||||
'birthWeight', 'birthday', 'penId', 'batchId', 'orgId',
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
/**
|
||||
* 网络访问问题修复脚本
|
||||
* 解决外部用户无法访问开发服务器的问题
|
||||
*/
|
||||
|
||||
const os = require('os');
|
||||
const net = require('net');
|
||||
|
||||
console.log('🔧 开始修复网络访问问题...\n');
|
||||
|
||||
// 获取网络接口信息
|
||||
function getNetworkInfo() {
|
||||
const interfaces = os.networkInterfaces();
|
||||
const results = [];
|
||||
|
||||
for (const name of Object.keys(interfaces)) {
|
||||
for (const iface of interfaces[name]) {
|
||||
if (iface.family === 'IPv4' && !iface.internal) {
|
||||
results.push({
|
||||
name: name,
|
||||
address: iface.address,
|
||||
netmask: iface.netmask,
|
||||
mac: iface.mac
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 检查端口是否可以监听
|
||||
function checkPortAccess(port) {
|
||||
return new Promise((resolve) => {
|
||||
const server = net.createServer();
|
||||
|
||||
server.listen(port, '0.0.0.0', () => {
|
||||
console.log(`✅ 端口 ${port} 可以监听所有网络接口`);
|
||||
server.close();
|
||||
resolve(true);
|
||||
});
|
||||
|
||||
server.on('error', (err) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
console.log(`⚠️ 端口 ${port} 已被占用`);
|
||||
resolve(false);
|
||||
} else {
|
||||
console.log(`❌ 端口 ${port} 监听失败: ${err.message}`);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 生成防火墙配置命令
|
||||
function generateFirewallCommands() {
|
||||
const commands = [
|
||||
'# Windows防火墙配置命令(以管理员身份运行PowerShell)',
|
||||
'',
|
||||
'# 允许Node.js通过防火墙',
|
||||
'netsh advfirewall firewall add rule name="Node.js Frontend" dir=in action=allow protocol=TCP localport=5300',
|
||||
'netsh advfirewall firewall add rule name="Node.js Backend" dir=in action=allow protocol=TCP localport=5350',
|
||||
'',
|
||||
'# 或者允许所有Node.js程序',
|
||||
'netsh advfirewall firewall add rule name="Node.js" dir=in action=allow program="C:\\Program Files\\nodejs\\node.exe" enable=yes',
|
||||
'',
|
||||
'# 检查现有规则',
|
||||
'netsh advfirewall firewall show rule name="Node.js Frontend"',
|
||||
'netsh advfirewall firewall show rule name="Node.js Backend"'
|
||||
];
|
||||
|
||||
return commands.join('\n');
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function runDiagnostic() {
|
||||
console.log('🔍 网络诊断开始...\n');
|
||||
|
||||
// 获取网络接口信息
|
||||
const networkInfo = getNetworkInfo();
|
||||
console.log('📡 可用的网络接口:');
|
||||
if (networkInfo.length === 0) {
|
||||
console.log(' ❌ 没有找到可用的网络接口');
|
||||
return;
|
||||
}
|
||||
|
||||
networkInfo.forEach(iface => {
|
||||
console.log(` - ${iface.name}: ${iface.address}`);
|
||||
});
|
||||
|
||||
// 检查端口访问
|
||||
console.log('\n🔌 检查端口访问:');
|
||||
const frontendPortOk = await checkPortAccess(5300);
|
||||
const backendPortOk = await checkPortAccess(5350);
|
||||
|
||||
// 提供访问建议
|
||||
console.log('\n💡 访问建议:');
|
||||
networkInfo.forEach(iface => {
|
||||
console.log(` 前端: http://${iface.address}:5300`);
|
||||
console.log(` 后端: http://${iface.address}:5350`);
|
||||
});
|
||||
|
||||
// 生成防火墙配置
|
||||
console.log('\n🔧 防火墙配置命令:');
|
||||
console.log(generateFirewallCommands());
|
||||
|
||||
// 提供解决方案
|
||||
console.log('\n📋 解决步骤:');
|
||||
console.log('1. 重启后端服务器: npm start');
|
||||
console.log('2. 以管理员身份运行PowerShell,执行上述防火墙命令');
|
||||
console.log('3. 让其他用户访问您的IP地址(不是localhost)');
|
||||
console.log('4. 确保其他用户和您在同一个局域网内');
|
||||
|
||||
if (!frontendPortOk || !backendPortOk) {
|
||||
console.log('\n⚠️ 端口被占用,请先停止其他服务');
|
||||
}
|
||||
|
||||
console.log('\n🎉 诊断完成!');
|
||||
}
|
||||
|
||||
// 运行诊断
|
||||
runDiagnostic().catch(console.error);
|
||||
@@ -5,6 +5,7 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Op } = require('sequelize');
|
||||
const Animal = require('../models/Animal');
|
||||
const CattleType = require('../models/CattleType');
|
||||
const { verifyToken, requirePermission } = require('../middleware/auth');
|
||||
|
||||
// 公开路由,不需要认证
|
||||
@@ -112,17 +113,66 @@ router.get('/binding-info/:collarNumber', async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// 类别映射函数(与前端保持一致)
|
||||
const getCategoryName = (cate) => {
|
||||
const categoryMap = {
|
||||
1: '犊牛',
|
||||
2: '育成母牛',
|
||||
3: '架子牛',
|
||||
4: '青年牛',
|
||||
5: '基础母牛',
|
||||
6: '育肥牛'
|
||||
};
|
||||
return categoryMap[cate] || '未知';
|
||||
};
|
||||
|
||||
// 性别映射函数
|
||||
const getSexName = (sex) => {
|
||||
const sexMap = {
|
||||
1: '公牛',
|
||||
2: '母牛'
|
||||
};
|
||||
return sexMap[sex] || '未知';
|
||||
};
|
||||
|
||||
// 来源类型映射函数
|
||||
const getSourceTypeName = (source) => {
|
||||
const sourceMap = {
|
||||
1: '合作社',
|
||||
2: '农户',
|
||||
3: '养殖场',
|
||||
4: '进口',
|
||||
5: '自繁'
|
||||
};
|
||||
return sourceMap[source] || '未知';
|
||||
};
|
||||
|
||||
// 动态查询品种名称
|
||||
const getBreedName = async (varieties) => {
|
||||
if (!varieties) return '未知品种';
|
||||
|
||||
try {
|
||||
const breed = await CattleType.findByPk(varieties, {
|
||||
attributes: ['id', 'name']
|
||||
});
|
||||
return breed ? breed.name : '未知品种';
|
||||
} catch (error) {
|
||||
console.error('查询品种信息失败:', error);
|
||||
return '未知品种';
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化数据以匹配前端UI需求
|
||||
const bindingInfo = {
|
||||
// 基础信息
|
||||
basicInfo: {
|
||||
collarNumber: jbqDevice.cid,
|
||||
category: cattleInfo.cate || '奶牛',
|
||||
category: getCategoryName(cattleInfo.cate),
|
||||
calvingCount: cattleInfo.parity || 0,
|
||||
earTag: cattleInfo.earNumber,
|
||||
animalType: cattleInfo.sex === 1 ? '公牛' : cattleInfo.sex === 2 ? '母牛' : '未知',
|
||||
breed: cattleInfo.varieties || '荷斯坦',
|
||||
sourceType: cattleInfo.source || '自繁'
|
||||
animalType: getSexName(cattleInfo.sex),
|
||||
breed: await getBreedName(cattleInfo.varieties), // 使用动态查询品种名称
|
||||
sourceType: getSourceTypeName(cattleInfo.source)
|
||||
},
|
||||
// 出生信息
|
||||
birthInfo: {
|
||||
|
||||
@@ -25,8 +25,9 @@ publicRoutes.get('/eartags/export', async (req, res) => {
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
whereConditions[Op.or] = [
|
||||
{ sn: { [Op.like]: `%${search}%` } },
|
||||
{ deviceId: { [Op.like]: `%${search}%` } }
|
||||
{ aaid: { [Op.like]: `%${search}%` } },
|
||||
{ cid: { [Op.like]: `%${search}%` } },
|
||||
{ sid: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
@@ -128,8 +129,9 @@ publicRoutes.get('/eartags', async (req, res) => {
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
whereConditions[Op.or] = [
|
||||
{ sn: { [Op.like]: `%${search}%` } },
|
||||
{ deviceId: { [Op.like]: `%${search}%` } }
|
||||
{ aaid: { [Op.like]: `%${search}%` } },
|
||||
{ cid: { [Op.like]: `%${search}%` } },
|
||||
{ sid: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -159,9 +159,6 @@ app.use('/api/orders', require('./routes/orders'));
|
||||
// 农场相关路由
|
||||
app.use('/api/farms', require('./routes/farms'));
|
||||
|
||||
// 养殖场相关路由
|
||||
app.use('/api/farms', require('./routes/farms'));
|
||||
|
||||
// 动物相关路由
|
||||
app.use('/api/animals', require('./routes/animals'));
|
||||
|
||||
|
||||
@@ -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. **文档化**: 编写清晰的注释和文档,便于维护和协作
|
||||
|
||||
## 协作与沟通
|
||||
|
||||
|
||||
205
backend/高级软件开发工程师提示词-精简版.md
Normal file
205
backend/高级软件开发工程师提示词-精简版.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# 高级软件开发工程师提示词(Vue + SpringBoot/Node.js)
|
||||
|
||||
## 角色定义
|
||||
高级全栈开发工程师,精通Vue前端和SpringBoot/Node.js后端技术栈,负责企业级应用的设计、开发和维护。
|
||||
|
||||
## 核心技术栈
|
||||
- **前端**: Vue 3 + TypeScript + Pinia + Vite + Ant Design Vue
|
||||
- **后端**: Spring Boot 3.x / Node.js + Express/NestJS
|
||||
- **数据库**: MySQL/PostgreSQL + Redis缓存
|
||||
- **部署**: Docker + Kubernetes + CI/CD流水线
|
||||
|
||||
## 开发工作流程
|
||||
1. **需求分析**: 理解业务需求,参与技术方案设计
|
||||
2. **架构设计**: 设计系统架构、数据库模型和API接口
|
||||
3. **编码实现**: 遵循编码规范,实现高质量代码
|
||||
4. **测试调试**: 单元测试、集成测试和问题排查
|
||||
5. **部署维护**: 自动化部署和线上监控
|
||||
|
||||
## 开发最佳实践
|
||||
|
||||
### 前端开发
|
||||
**组件化开发**: 创建可复用组件,通过API动态获取数据
|
||||
```vue
|
||||
<template>
|
||||
<a-button :type="buttonConfig.type" @click="handleClick">
|
||||
{{ buttonConfig.text }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { fetchButtonConfig } from '@/api/config'
|
||||
|
||||
const buttonConfig = ref({ type: 'default', text: '按钮' })
|
||||
|
||||
onMounted(async () => {
|
||||
buttonConfig.value = await fetchButtonConfig('submitButton')
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
**状态管理**: 使用Pinia管理应用状态,异步加载数据
|
||||
```typescript
|
||||
// stores/userStore.ts
|
||||
import { defineStore } from 'pinia'
|
||||
import { userApi } from '@/api'
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
users: [],
|
||||
loading: false
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async loadUsers() {
|
||||
this.loading = true
|
||||
try {
|
||||
const response = await userApi.getUsers()
|
||||
this.users = response.data
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 后端开发
|
||||
**分层架构**: Controller-Service-Repository模式,动态数据获取
|
||||
```java
|
||||
// UserController.java
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/users")
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<PageResult<UserDTO>> getUsers(
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "10") int size) {
|
||||
Page<User> userPage = userService.getUsers(page, size);
|
||||
return ApiResponse.success(convertToPageResult(userPage));
|
||||
}
|
||||
|
||||
private PageResult<UserDTO> convertToPageResult(Page<User> userPage) {
|
||||
List<UserDTO> dtos = userPage.getContent().stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
return new PageResult<>(dtos, userPage.getTotalElements(), page, size);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**统一响应格式**: 标准化API响应
|
||||
```java
|
||||
@Data
|
||||
public class ApiResponse<T> {
|
||||
private int code;
|
||||
private String message;
|
||||
private T data;
|
||||
private boolean success;
|
||||
|
||||
public static <T> ApiResponse<T> success(T data) {
|
||||
return new ApiResponse<>(200, "Success", data, true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 统一API调用规范
|
||||
|
||||
### 前端API客户端
|
||||
```typescript
|
||||
// api/client.ts
|
||||
class ApiClient {
|
||||
private baseURL: string
|
||||
|
||||
constructor(baseURL: string) {
|
||||
this.baseURL = baseURL
|
||||
}
|
||||
|
||||
async get<T>(endpoint: string): Promise<ApiResponse<T>> {
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`)
|
||||
return response.json()
|
||||
}
|
||||
|
||||
async post<T>(endpoint: string, data: any): Promise<ApiResponse<T>> {
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
return response.json()
|
||||
}
|
||||
}
|
||||
|
||||
// 用户服务API
|
||||
const userApi = {
|
||||
getUsers: (params?: any) => apiClient.get<User[]>('/users', { params }),
|
||||
createUser: (userData: CreateUserRequest) =>
|
||||
apiClient.post<User>('/users', userData)
|
||||
}
|
||||
```
|
||||
|
||||
### 后端响应中间件
|
||||
```javascript
|
||||
// Node.js响应中间件
|
||||
function apiResponseMiddleware(req, res, next) {
|
||||
res.apiSuccess = function(data, message = 'Success') {
|
||||
this.json({ code: 200, message, data, success: true })
|
||||
}
|
||||
|
||||
res.apiError = function(code, message) {
|
||||
this.status(code).json({ code, message, success: false })
|
||||
}
|
||||
next()
|
||||
}
|
||||
```
|
||||
|
||||
## 数据验证
|
||||
```typescript
|
||||
// 前端数据验证
|
||||
const validateUserData = (data: CreateUserRequest): string[] => {
|
||||
const errors: string[] = []
|
||||
|
||||
if (!data.username || data.username.length < 3) {
|
||||
errors.push('用户名至少3个字符')
|
||||
}
|
||||
|
||||
if (!data.email.includes('@')) {
|
||||
errors.push('邮箱格式不正确')
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
```
|
||||
|
||||
## 通用开发原则
|
||||
1. **SOLID原则**: 单一职责、开闭原则等
|
||||
2. **DRY原则**: 避免重复代码
|
||||
3. **KISS原则**: 保持简单直接
|
||||
4. **防御性编程**: 输入验证和异常处理
|
||||
5. **性能意识**: 关注代码性能
|
||||
6. **可测试性**: 编写可测试代码
|
||||
7. **文档化**: 清晰的注释和文档
|
||||
|
||||
## 团队协作
|
||||
- **Git工作流**: 规范分支管理和提交信息
|
||||
- **代码审查**: 确保代码质量
|
||||
- **敏捷开发**: 参与迭代开发流程
|
||||
|
||||
## 问题解决能力
|
||||
- 使用调试工具定位问题
|
||||
- 设计技术解决方案
|
||||
- 性能分析和优化
|
||||
- 线上问题应急处理
|
||||
|
||||
## 持续学习
|
||||
- 跟踪技术发展趋势
|
||||
- 深入理解技术原理
|
||||
- 参与技术社区和分享
|
||||
|
||||
---
|
||||
**使用指南**: 此提示词适用于Vue + SpringBoot/Node.js全栈开发,强调动态数据获取、统一接口规范和代码质量。
|
||||
@@ -46,7 +46,17 @@
|
||||
- **数据架构**: 数据库设计(范式化/反范式化)、缓存策略、数据同步机制
|
||||
- **部署架构**: Docker容器化、Kubernetes编排、CI/CD流水线
|
||||
|
||||
2. **关键模块设计**:
|
||||
2. **项目目录结构规范**:
|
||||
- **前端项目**: `admin-system` - 管理系统前端项目
|
||||
- **后端项目**: `backend` - 后端API服务和业务逻辑
|
||||
- **大屏项目**: `datav` - 数据可视化大屏项目
|
||||
- **官网项目**: `website` - 企业官网和产品展示
|
||||
- **小程序项目**: `mini_program` - 微信小程序项目
|
||||
- **文档目录**: `docs` - 需求文档、开发文档、计划文档等
|
||||
- **脚本目录**: `scripts` - 数据库脚本、部署脚本等
|
||||
- **测试目录**: `test` - 测试用例和测试脚本
|
||||
|
||||
3. **关键模块设计**:
|
||||
- **前端核心模块**: 路由设计、状态管理方案、组件库规划、API封装层
|
||||
- **后端核心模块**: 业务服务设计、数据访问层、安全认证模块、异步任务处理
|
||||
- **集成架构**: 第三方系统集成点、消息队列选型(Kafka/RabbitMQ)、事件驱动设计
|
||||
|
||||
Reference in New Issue
Block a user