| name | ruoyi-vue-plus-code-patterns |
| description | 基于若依-vue-plus框架的全栈代码规范指南。定义后端Java Spring Boot与前端Vue3 + TypeScript的标准化编码模式。
核心内容:
- 后端分层架构规范:Controller/Service/Mapper三层设计,对象流转模式(Bo/DTO → Entity → Vo)
- 前端Vue3组合式API规范:<script setup>语法糖、响应式数据管理、组件通信模式
- 数据库操作规范:MyBatis-Plus的使用规范、批量操作、事务管理
- 统一响应处理:AjaxResult封装、异常处理、日志记录
- 安全与权限:@PreAuthorize注解、数据权限、敏感字段处理
触发场景:
- 编写新的业务模块(CRUD功能)
- 重构旧代码或优化现有代码
- 进行代码审查(Code Review)
- 解决分层架构设计问题
- 处理Vue3响应式数据丢失问题
触发关键词:代码规范、若依框架、分层架构、DTO/VO转换、对象映射、Vue3 Setup、Composition API、组件封装、响应式、MyBatis-Plus
|
若依框架代码规范指南
核心规范
规范1:分层架构与对象映射规范
架构层次划分:
- Controller层(控制器层):负责接收HTTP请求、参数校验、权限控制、响应封装
- Service层(业务逻辑层):负责业务逻辑处理、事务控制、数据组装
- Mapper层(数据访问层):负责数据库CRUD操作,仅处理数据持久化
对象流转规范:
-
Bo (Business Object) / DTO (Data Transfer Object):前端传入的数据传输对象
- 用于接收前端表单数据
- 包含校验注解(@NotNull、@NotBlank等)
- 命名规则:
实体名称Bo.java(如 SysOrderBo.java)
-
Entity (实体对象):数据库表映射对象
- 与数据库表结构一一对应
- 仅在Service层和Mapper层使用
- 包含MyBatis-Plus注解(@TableName、@TableId等)
- 命名规则:
实体名称.java(如 SysOrder.java)
-
Vo (View Object):返回给前端的视图对象
- 用于返回给前端展示的数据
- 可能包含关联对象、计算字段、格式化数据
- 严禁直接返回Entity,避免暴露敏感字段(密码、盐值等)
- 命名规则:
实体名称Vo.java(如 SysOrderVo.java)
对象转换工具:
- 优先使用:
BeanUtil.copyProperties(source, targetClass)(若依封装的Hutool工具)
- 复杂场景使用:MapStruct(编译期生成转换代码,性能更优)
- 严禁手写大量setter/getter进行属性拷贝
完整数据流转示例:
前端请求 → Bo/DTO(Controller接收) → Entity(Service处理) → Vo(Controller返回) → 前端展示
Controller层完整示例:
package com.ruoyi.web.controller.system;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.bean.BeanUtil;
import com.ruoyi.system.domain.SysOrder;
import com.ruoyi.system.domain.bo.SysOrderBo;
import com.ruoyi.system.domain.vo.SysOrderVo;
import com.ruoyi.system.service.ISysOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/system/order")
public class SysOrderController extends BaseController {
@Autowired
private ISysOrderService orderService;
@PreAuthorize("@ss.hasPermi('system:order:list')")
@GetMapping("/list")
public TableDataInfo list(SysOrderBo bo) {
startPage();
List<SysOrderVo> list = orderService.selectOrderList(bo);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('system:order:query')")
@GetMapping(value = "/{orderId}")
public AjaxResult getInfo(@PathVariable Long orderId) {
return success(orderService.selectOrderById(orderId));
}
@PreAuthorize("@ss.hasPermi('system:order:add')")
@Log(title = "订单管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody SysOrderBo bo) {
SysOrder order = BeanUtil.copyProperties(bo, SysOrder.class);
boolean result = orderService.insertOrder(order);
return toAjax(result);
}
@PreAuthorize("@ss.hasPermi('system:order:edit')")
@Log(title = "订单管理", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysOrderBo bo) {
SysOrder order = BeanUtil.copyProperties(bo, SysOrder.class);
return toAjax(orderService.updateOrder(order));
}
@PreAuthorize("@ss.hasPermi('system:order:remove')")
@Log(title = "订单管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{orderIds}")
public AjaxResult remove(@PathVariable Long[] orderIds) {
return toAjax(orderService.deleteOrderByIds(orderIds));
}
@PreAuthorize("@ss.hasPermi('system:order:export')")
@Log(title = "订单管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SysOrderBo bo) {
List<SysOrderVo> list = orderService.selectOrderList(bo);
ExcelUtil<SysOrderVo> util = new ExcelUtil<>(SysOrderVo.class);
util.exportExcel(response, list, "订单数据");
}
}
Service层实现示例:
package com.ruoyi.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtil;
import com.ruoyi.system.domain.SysOrder;
import com.ruoyi.system.domain.bo.SysOrderBo;
import com.ruoyi.system.domain.vo.SysOrderVo;
import com.ruoyi.system.mapper.SysOrderMapper;
import com.ruoyi.system.service.ISysOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class SysOrderServiceImpl implements ISysOrderService {
@Autowired
private SysOrderMapper orderMapper;
@Override
public List<SysOrderVo> selectOrderList(SysOrderBo bo) {
LambdaQueryWrapper<SysOrder> wrapper = Wrappers.lambdaQuery();
wrapper.like(StringUtils.isNotBlank(bo.getOrderNo()),
SysOrder::getOrderNo, bo.getOrderNo())
.eq(bo.getStatus() != null,
SysOrder::getStatus, bo.getStatus())
.between(bo.getBeginTime() != null && bo.getEndTime() != null,
SysOrder::getCreateTime, bo.getBeginTime(), bo.getEndTime());
List<SysOrder> list = orderMapper.selectList(wrapper);
return list.stream()
.map(entity -> BeanUtil.copyProperties(entity, SysOrderVo.class))
.collect(Collectors.toList());
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean insertOrder(SysOrder order) {
order.setOrderNo(generateOrderNo());
order.setCreateBy(SecurityUtils.getUsername());
return orderMapper.insert(order) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteOrderByIds(Long[] orderIds) {
return orderMapper.deleteBatchIds(Arrays.asList(orderIds)) > 0;
}
private String generateOrderNo() {
return "ORD" + System.currentTimeMillis();
}
}
规范2:Vue3 组合式 API (Composition API) 编码标准
核心原则:
- 所有新组件必须使用
<script setup lang="ts"> 语法糖
- 禁止使用 Options API(
export default {})编写新组件
- 优先使用 TypeScript 提供类型安全
响应式数据定义规范:
-
ref() - 用于基本类型和单一对象引用
- 适用场景:字符串、数字、布尔值、单个对象引用
- 访问方式:通过
.value 访问/修改
- 模板中自动解包,无需
.value
-
reactive() - 用于复杂对象和表单数据
- 适用场景:表单对象、查询参数、状态管理
- 特性:深度响应式,自动解包嵌套的ref
- 注意:解构会丢失响应性,需使用
toRefs() 转换
-
toRefs() - 保持解构后的响应性
- 使用场景:需要解构 reactive 对象时
- 作用:将 reactive 对象的每个属性转为 ref
组件通信规范:
-
父传子:defineProps(类型定义)
interface Props {
orderId: number;
orderData?: SysOrder;
}
const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
orderData: () => ({})
});
-
子传父:defineEmits(事件定义)
interface Emits {
(e: 'update:modelValue', value: string): void;
(e: 'submit', data: FormData): void;
}
const emit = defineEmits<Emits>();
emit('submit', formData);
-
跨层级通信:provide/inject(谨慎使用)
- 仅用于深层组件树共享状态
- 优先使用 props/emits 保持数据流清晰
生命周期钩子:
onMounted() - 组件挂载后(替代 mounted)
onBeforeUnmount() - 组件卸载前(替代 beforeDestroy)
onUpdated() - 组件更新后
watch() / watchEffect() - 侦听响应式数据变化
完整示例:
<template>
<div class="app-container">
<!-- 搜索栏 -->
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch">
<el-form-item label="订单编号" prop="orderNo">
<el-input
v-model="queryParams.orderNo"
placeholder="请输入订单编号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="订单状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择订单状态" clearable>
<el-option
v-for="dict in sys_order_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['system:order:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['system:order:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:order:remove']"
>删除</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="orderList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="订单编号" align="center" prop="orderNo" />
<el-table-column label="订单金额" align="center" prop="amount" />
<el-table-column label="订单状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_order_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button
type="text"
icon="Edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:order:edit']"
>修改</el-button>
<el-button
type="text"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:order:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改订单对话框 -->
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="订单编号" prop="orderNo">
<el-input v-model="form.orderNo" placeholder="请输入订单编号" />
</el-form-item>
<el-form-item label="订单金额" prop="amount">
<el-input-number v-model="form.amount" :precision="2" :min="0" />
</el-form-item>
<el-form-item label="订单状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_order_status"
:key="dict.value"
:label="dict.value"
>{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="OrderList">
import { ref, reactive, toRefs, computed, onMounted, nextTick } from 'vue';
import { listOrder, getOrder, addOrder, updateOrder, delOrder } from '@/api/system/order';
import { SysOrderVo, SysOrderForm, SysOrderQuery } from '@/api/system/order/types';
import type { FormInstance, FormRules } from 'element-plus';
// ==================== 1. 响应式数据定义 ====================
// 基本类型使用 ref
const loading = ref(false);
const showSearch = ref(true);
const open = ref(false);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref('');
// 列表数据使用 ref
const orderList = ref<SysOrderVo[]>([]);
const ids = ref<Array<string | number>>([]);
// 表单对象使用 reactive(配合双向绑定)
const queryParams = reactive<SysOrderQuery>({
pageNum: 1,
pageSize: 10,
orderNo: '',
status: undefined,
beginTime: undefined,
endTime: undefined
});
// 表单数据使用 reactive
const form = reactive<SysOrderForm>({
orderId: undefined,
orderNo: '',
amount: 0,
status: '0',
remark: ''
});
// 表单校验规则
const rules = reactive<FormRules>({
orderNo: [
{ required: true, message: '订单编号不能为空', trigger: 'blur' }
],
amount: [
{ required: true, message: '订单金额不能为空', trigger: 'blur' }
],
status: [
{ required: true, message: '订单状态不能为空', trigger: 'change' }
]
});
// ==================== 2. 引用和字典 ====================
// 表单引用
const queryFormRef = ref<FormInstance>();
const formRef = ref<FormInstance>();
// 字典数据(从store获取或本地定义)
const { sys_order_status } = proxy.useDict('sys_order_status');
// ==================== 3. 计算属性 ====================
// 示例:计算已选中的订单数量
const selectedCount = computed(() => ids.value.length);
// ==================== 4. 方法定义 ====================
/** 查询订单列表 */
const getList = async () => {
loading.value = true;
try {
const res = await listOrder(queryParams);
orderList.value = res.rows;
total.value = res.total;
} finally {
loading.value = false;
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: SysOrderVo[]) => {
ids.value = selection.map(item => item.orderId);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
open.value = true;
title.value = '添加订单';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: SysOrderVo) => {
reset();
const orderId = row?.orderId || ids.value[0];
try {
const res = await getOrder(orderId);
Object.assign(form, res.data);
open.value = true;
title.value = '修改订单';
} catch (error) {
proxy.$modal.msgError('获取订单详情失败');
}
};
/** 提交按钮 */
const submitForm = async () => {
const valid = await formRef.value?.validate();
if (!valid) return;
try {
if (form.orderId) {
await updateOrder(form);
proxy.$modal.msgSuccess('修改成功');
} else {
await addOrder(form);
proxy.$modal.msgSuccess('新增成功');
}
open.value = false;
await getList();
} catch (error) {
proxy.$modal.msgError('操作失败');
}
};
/** 删除按钮操作 */
const handleDelete = async (row?: SysOrderVo) => {
const orderIds = row?.orderId ? [row.orderId] : ids.value;
try {
await proxy.$modal.confirm(`是否确认删除订单编号为"${orderIds}"的数据项?`);
await delOrder(orderIds);
proxy.$modal.msgSuccess('删除成功');
await getList();
} catch {}
};
/** 取消按钮 */
const cancel = () => {
open.value = false;
reset();
};
/** 表单重置 */
const reset = () => {
Object.assign(form, {
orderId: undefined,
orderNo: '',
amount: 0,
status: '0',
remark: ''
});
formRef.value?.resetFields();
};
// ==================== 5. 生命周期钩子 ====================
onMounted(() => {
getList();
});
// ==================== 6. 暴露给模板的方法(可选)====================
// defineExpose({
// getList
// });
</script>
<style scoped lang="scss">
// 组件样式
</style>
组件封装示例(子组件):
<template>
<el-dialog
:title="title"
v-model="dialogVisible"
width="500px"
@close="handleClose"
>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="订单编号" prop="orderNo">
<el-input v-model="formData.orderNo" placeholder="请输入订单编号" />
</el-form-item>
<el-form-item label="订单金额" prop="amount">
<el-input-number v-model="formData.amount" :min="0" :precision="2" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="OrderDialog">
import { ref, reactive, watch } from 'vue';
import type { FormInstance, FormRules } from 'element-plus';
// ==================== Props 定义 ====================
interface Props {
visible: boolean;
orderId?: number;
title?: string;
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
title: '订单详情'
});
// ==================== Emits 定义 ====================
interface Emits {
(e: 'update:visible', value: boolean): void;
(e: 'confirm', data: any): void;
}
const emit = defineEmits<Emits>();
// ==================== 响应式数据 ====================
const formRef = ref<FormInstance>();
const dialogVisible = ref(props.visible);
const formData = reactive({
orderNo: '',
amount: 0
});
const rules = reactive<FormRules>({
orderNo: [{ required: true, message: '请输入订单编号', trigger: 'blur' }],
amount: [{ required: true, message: '请输入订单金额', trigger: 'blur' }]
});
// ==================== 侦听器 ====================
// 侦听 props.visible 变化,同步到内部状态
watch(() => props.visible, (newVal) => {
dialogVisible.value = newVal;
});
// 侦听内部状态变化,同步到父组件
watch(dialogVisible, (newVal) => {
emit('update:visible', newVal);
});
// ==================== 方法 ====================
const handleConfirm = async () => {
const valid = await formRef.value?.validate();
if (valid) {
emit('confirm', { ...formData });
handleClose();
}
};
const handleCancel = () => {
handleClose();
};
const handleClose = () => {
dialogVisible.value = false;
formRef.value?.resetFields();
};
// ==================== 暴露方法给父组件 ====================
defineExpose({
// 父组件可以通过 ref 调用这些方法
resetForm: () => formRef.value?.resetFields()
});
</script>
使用子组件示例:
<template>
<div>
<el-button @click="openDialog">打开对话框</el-button>
<!-- 使用 v-model:visible 双向绑定 -->
<OrderDialog
ref="orderDialogRef"
v-model:visible="dialogVisible"
:order-id="currentOrderId"
title="编辑订单"
@confirm="handleDialogConfirm"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import OrderDialog from './components/OrderDialog.vue';
const dialogVisible = ref(false);
const currentOrderId = ref<number>();
const orderDialogRef = ref<InstanceType<typeof OrderDialog>>();
const openDialog = () => {
currentOrderId.value = 123;
dialogVisible.value = true;
};
const handleDialogConfirm = (data: any) => {
console.log('接收到的数据:', data);
// 处理业务逻辑
};
// 调用子组件暴露的方法
const resetDialog = () => {
orderDialogRef.value?.resetForm();
};
</script>
规范3:MyBatis-Plus 使用规范
核心原则:
- 优先使用 MyBatis-Plus 提供的 CRUD 方法,减少 XML 配置
- 复杂查询使用 LambdaQueryWrapper,避免字符串拼接
- 批量操作使用批处理方法,提升性能
- 自定义 SQL 放在 Mapper.xml 中,保持代码整洁
常用查询方法:
SysOrder order = orderMapper.selectById(orderId);
List<SysOrder> list = orderMapper.selectList(null);
LambdaQueryWrapper<SysOrder> wrapper = Wrappers.lambdaQuery();
wrapper.eq(SysOrder::getOrderNo, "ORD123")
.like(SysOrder::getCustomerName, "张三")
.between(SysOrder::getAmount, 100, 1000)
.in(SysOrder::getStatus, Arrays.asList(1, 2))
.isNotNull(SysOrder::getCreateTime)
.orderByDesc(SysOrder::getCreateTime);
List<SysOrder> orders = orderMapper.selectList(wrapper);
wrapper.like(StringUtils.isNotBlank(orderNo), SysOrder::getOrderNo, orderNo)
.eq(status != null, SysOrder::getStatus, status)
.ge(beginTime != null, SysOrder::getCreateTime, beginTime)
.le(endTime != null, SysOrder::getCreateTime, endTime);
Page<SysOrder> page = new Page<>(pageNum, pageSize);
IPage<SysOrder> result = orderMapper.selectPage(page, wrapper);
wrapper.select(SysOrder::getOrderId, SysOrder::getOrderNo, SysOrder::getAmount);
Long count = orderMapper.selectCount(wrapper);
增删改操作:
SysOrder order = new SysOrder();
order.setOrderNo("ORD123");
order.setAmount(new BigDecimal("999.99"));
orderMapper.insert(order);
order.setStatus(2);
orderMapper.updateById(order);
LambdaUpdateWrapper<SysOrder> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.set(SysOrder::getStatus, 2)
.eq(SysOrder::getOrderNo, "ORD123");
orderMapper.update(null, updateWrapper);
orderMapper.deleteById(orderId);
orderMapper.deleteBatchIds(Arrays.asList(1L, 2L, 3L));
LambdaQueryWrapper<SysOrder> deleteWrapper = Wrappers.lambdaQuery();
deleteWrapper.eq(SysOrder::getStatus, 0);
orderMapper.delete(deleteWrapper);
批量操作(高性能):
List<SysOrder> orderList = new ArrayList<>();
orderService.saveBatch(orderList);
orderService.updateBatchById(orderList);
orderService.saveBatch(orderList, 500);
自定义 SQL(Mapper.xml):
<mapper namespace="com.ruoyi.system.mapper.SysOrderMapper">
<resultMap id="SysOrderResult" type="SysOrder">
<id property="orderId" column="order_id" />
<result property="orderNo" column="order_no" />
<result property="amount" column="amount" />
<result property="status" column="status" />
<result property="createTime" column="create_time" />
</resultMap>
<select id="selectOrderWithProducts" resultType="SysOrderVo">
SELECT
o.order_id,
o.order_no,
o.amount,
GROUP_CONCAT(p.product_name) as product_names
FROM sys_order o
LEFT JOIN sys_order_product op ON o.order_id = op.order_id
LEFT JOIN sys_product p ON op.product_id = p.product_id
WHERE o.del_flag = '0'
<if test="orderNo != null and orderNo != ''">
AND o.order_no LIKE CONCAT('%', #{orderNo}, '%')
</if>
GROUP BY o.order_id
ORDER BY o.create_time DESC
</select>
<insert id="insertBatch">
INSERT INTO sys_order (order_no, amount, status, create_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.orderNo}, #{item.amount}, #{item.status}, #{item.createTime})
</foreach>
</insert>
</mapper>
Mapper 接口定义:
@Mapper
public interface SysOrderMapper extends BaseMapper<SysOrder> {
List<SysOrderVo> selectOrderWithProducts(@Param("orderNo") String orderNo);
int insertBatch(@Param("list") List<SysOrder> orderList);
}
规范4:事务管理规范
事务注解使用:
@Service
public class SysOrderServiceImpl implements ISysOrderService {
@Override
@Transactional(rollbackFor = Exception.class)
public boolean createOrder(SysOrderBo bo) {
SysOrder order = BeanUtil.copyProperties(bo, SysOrder.class);
orderMapper.insert(order);
List<OrderDetail> details = bo.getDetails();
for (OrderDetail detail : details) {
detail.setOrderId(order.getOrderId());
orderDetailMapper.insert(detail);
}
for (OrderDetail detail : details) {
productService.reduceStock(detail.getProductId(), detail.getQuantity());
}
return true;
}
@Override
@Transactional(readOnly = true)
public List<SysOrderVo> selectOrderList(SysOrderBo bo) {
return orderMapper.selectOrderList(bo);
}
}
事务传播行为:
@Transactional(propagation = Propagation.REQUIRED)
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.NESTED)
规范5:异常处理与日志规范
统一异常处理:
@Service
public class SysOrderServiceImpl implements ISysOrderService {
private static final Logger log = LoggerFactory.getLogger(SysOrderServiceImpl.class);
@Override
public boolean insertOrder(SysOrder order) {
try {
orderMapper.insert(order);
return true;
} catch (DuplicateKeyException e) {
log.error("订单号重复: {}", order.getOrderNo(), e);
throw new ServiceException("订单号已存在");
} catch (Exception e) {
log.error("创建订单失败", e);
throw new ServiceException("创建订单失败,请联系管理员");
}
}
}
Controller 层异常处理(全局异常处理器已配置):
@RestController
public class SysOrderController {
@PostMapping
public AjaxResult add(@Validated @RequestBody SysOrderBo bo) {
return toAjax(orderService.insertOrder(order));
}
}
日志记录规范:
@Slf4j
@Service
public class SysOrderServiceImpl {
public void processOrder(Long orderId) {
log.debug("开始处理订单: {}", orderId);
log.info("订单处理成功: {}", orderId);
log.warn("订单库存不足: {}", orderId);
log.error("订单处理失败: {}", orderId, ex);
}
}
@Log(title = "订单管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SysOrderBo bo) {
return toAjax(orderService.insertOrder(order));
}
规范6:参数校验规范
DTO/Bo 对象校验:
import javax.validation.constraints.*;
public class SysOrderBo {
@NotNull(message = "订单ID不能为空", groups = {EditGroup.class})
private Long orderId;
@NotBlank(message = "订单编号不能为空")
@Size(max = 50, message = "订单编号长度不能超过50个字符")
private String orderNo;
@NotNull(message = "订单金额不能为空")
@DecimalMin(value = "0.01", message = "订单金额必须大于0")
@DecimalMax(value = "99999.99", message = "订单金额不能超过99999.99")
private BigDecimal amount;
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Email(message = "邮箱格式不正确")
private String email;
@NotEmpty(message = "订单明细不能为空")
@Valid
private List<OrderDetailBo> details;
}
Controller 层校验:
@PostMapping
public AjaxResult add(@Validated @RequestBody SysOrderBo bo) {
return toAjax(orderService.insertOrder(order));
}
@PostMapping
public AjaxResult add(@Validated(AddGroup.class) @RequestBody SysOrderBo bo) {
return toAjax(orderService.insertOrder(order));
}
@PutMapping
public AjaxResult edit(@Validated(EditGroup.class) @RequestBody SysOrderBo bo) {
return toAjax(orderService.updateOrder(order));
}
@GetMapping("/{orderId}")
public AjaxResult getInfo(@PathVariable @Min(value = 1, message = "订单ID必须大于0") Long orderId) {
return success(orderService.selectOrderById(orderId));
}
自定义校验注解:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = OrderStatusValidator.class)
public @interface ValidOrderStatus {
String message() default "订单状态值不合法";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class OrderStatusValidator implements ConstraintValidator<ValidOrderStatus, Integer> {
private static final Set<Integer> VALID_STATUS = Set.of(0, 1, 2, 3, 4);
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return VALID_STATUS.contains(value);
}
}
public class SysOrderBo {
@ValidOrderStatus(message = "订单状态只能是0-4之间的值")
private Integer status;
}
## 禁止事项
### 后端开发禁止事项
1. **❌ 对象流转违规**
- 禁止在Controller层直接返回数据库实体`Entity`,必须使用`Vo`进行数据裁剪
- 禁止Controller直接操作Mapper层,必须通过Service层处理业务逻辑
- 禁止在Entity中添加非数据库字段(应放在Vo中)
- 禁止手写大量的setter/getter进行对象拷贝,必须使用`BeanUtil.copyProperties`或MapStruct
2. **❌ Service层违规**
- 禁止在Service层中直接使用`HttpServletRequest`获取参数,必须通过Controller层传递
- 禁止在Service层返回`AjaxResult`,应返回业务对象或boolean
- 禁止Service方法抛出不明确的`Exception`,应抛出`ServiceException`并附带友好错误信息
- 禁止在无事务方法中进行多表操作,必须添加`@Transactional`注解
3. **❌ MyBatis-Plus违规**
- 禁止使用字符串拼接SQL(容易SQL注入),必须使用LambdaQueryWrapper
- 禁止在循环中执行单条insert/update(性能低下),应使用批量方法
- 禁止直接使用`selectList(null)`查询全表(数据量大会OOM),必须添加分页或限制条件
- 禁止在Entity上使用`@TableField(exist = false)`定义过多非数据库字段
4. **❌ 事务管理违规**
- 禁止忘记添加`rollbackFor = Exception.class`(默认只回滚RuntimeException)
- 禁止在事务方法中捕获异常后不抛出(会导致事务不回滚)
- 禁止在Controller层方法上添加`@Transactional`(应在Service层)
- 禁止事务方法调用同类中的另一个事务方法(事务会失效,应使用AOP代理)
5. **❌ 异常处理违规**
- 禁止捕获异常后只打印日志不处理(`e.printStackTrace()`或空catch块)
- 禁止抛出Exception基类,应抛出具体异常(`ServiceException`、`BusinessException`等)
- 禁止在finally块中使用return(会覆盖try块的返回值和异常)
- 禁止使用`System.out.println`打印日志,必须使用Logger
6. **❌ 日志记录违规**
- 禁止在生产代码中使用`System.out.println`,必须使用Slf4j
- 禁止使用字符串拼接记录日志(`log.info("用户:" + username)`),应使用占位符(`log.info("用户: {}", username)`)
- 禁止在高频调用的方法中使用`log.info`(应使用`log.debug`)
- 禁止记录敏感信息(密码、身份证号、银行卡号等)到日志
7. **❌ 安全违规**
- 禁止在Vo中返回密码、盐值等敏感字段
- 禁止在日志中打印完整的用户敏感信息
- 禁止在前端直接传递SQL语句或表名
- 禁止使用字符串拼接构造SQL(必须使用参数化查询)
8. **❌ 性能违规**
- 禁止在循环中调用数据库(N+1查询问题)
- 禁止查询大量数据不分页(应使用`PageHelper.startPage()`)
- 禁止在MyBatis的`<select>`中使用`SELECT *`(应明确指定字段)
- 禁止在高并发接口中使用synchronized同步代码块(应使用分布式锁或Redis)
### 前端开发禁止事项
1. **❌ Vue3语法违规**
- 禁止在Vue3中继续使用Options API(`export default {}`),必须使用`<script setup>`
- 禁止直接修改props(props是只读的),应通过emit通知父组件修改
- 禁止解构reactive对象而不使用`toRefs`(会丢失响应性)
- 禁止在模板中使用复杂的表达式(应使用computed计算属性)
2. **❌ 响应式数据违规**
- 禁止使用`var`声明变量,必须使用`const`或`let`
- 禁止直接给reactive对象赋值(`queryParams = {}`),应使用`Object.assign`或单独修改属性
- 禁止在ref对象上忘记使用`.value`(模板中会自动解包,但JS代码中需要)
- 禁止过度使用`any`类型,应明确定义TypeScript接口
3. **❌ API调用违规**
- 禁止在前端API调用中使用硬编码的URL,必须统一在`@/api/xxx`中定义
- 禁止不处理API异常(应使用try-catch或全局错误处理)
- 禁止在组件中直接调用axios,必须封装成API方法
- 禁止同步调用API(应使用async/await或Promise)
4. **❌ 组件设计违规**
- 禁止在一个组件中编写超过500行代码(应拆分成多个子组件)
- 禁止在子组件中直接修改父组件的数据(应使用emit事件)
- 禁止过度使用`provide/inject`(应优先使用props和emit)
- 禁止在组件中直接操作DOM(应使用ref和Vue的响应式系统)
5. **❌ 表单处理违规**
- 禁止不校验表单就提交(应使用`formRef.value?.validate()`)
- 禁止表单提交后不重置表单(应调用`resetFields()`)
- 禁止在v-model上绑定非响应式数据
- 禁止使用字符串作为表单字段名(应使用对象的属性名)
6. **❌ 性能优化违规**
- 禁止在computed中执行异步操作(应使用watch或watchEffect)
- 禁止在template中频繁调用方法(应使用computed缓存结果)
- 禁止不使用key绑定v-for循环(会导致渲染错误)
- 禁止在高频触发的事件中不使用防抖/节流(如input、scroll)
7. **❌ TypeScript违规**
- 禁止滥用`as any`强制类型转换(会失去类型检查)
- 禁止不定义API响应的接口类型(应在`types.ts`中定义)
- 禁止使用隐式any(应开启`strict`模式)
- 禁止在接口中使用可选属性而不提供默认值(容易出现undefined错误)
8. **❌ 调试代码违规**
- 禁止在生产代码中保留`console.log`(应在构建时移除或使用环境变量控制)
- 禁止在生产代码中保留`debugger`语句
- 禁止提交注释掉的大段代码(应使用Git版本控制)
- 禁止在代码中硬编码测试数据
## 参考代码与最佳实践
### 若依框架参考文件
#### 后端参考文件
- **Controller层示例**:`ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java`
- 标准的CRUD操作示例
- 权限注解使用
- 日志记录注解使用
- 分页查询处理
- **Service层示例**:`ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java`
- 业务逻辑处理
- 事务管理
- 对象转换
- 异常处理
- **Mapper层示例**:`ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java`
- MyBatis-Plus基础用法
- 自定义SQL方法
- **实体类示例**:
- Entity: `ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUser.java`
- Bo: `ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysUserBo.java`
- Vo: `ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysUserVo.java`
- **工具类**:
- BeanUtil: `ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtil.java`
- StringUtils: `ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java`
- SecurityUtils: `ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java`
#### 前端参考文件
- **Vue3页面示例**:`ruoyi-ui/src/views/system/user/index.vue`
- 完整的CRUD页面
- 表格、表单、对话框组件使用
- 分页处理
- 权限按钮控制
- **API封装示例**:`ruoyi-ui/src/api/system/user.js`
- 统一的API接口定义
- 请求参数封装
- 响应数据处理
- **类型定义示例**:`ruoyi-ui/src/api/system/user/types.ts`
- TypeScript接口定义
- 请求/响应类型
- **组件封装示例**:
- 字典标签: `ruoyi-ui/src/components/DictTag/index.vue`
- 分页组件: `ruoyi-ui/src/components/Pagination/index.vue`
- 文件上传: `ruoyi-ui/src/components/FileUpload/index.vue`
### 命名规范
#### 后端命名规范
```java
com.ruoyi.system.controller
com.ruoyi.system.service.impl
public class SysUserController {}
public class SysUserServiceImpl {}
public List<SysUserVo> selectUserList() {}
public boolean insertUser(SysUser user) {}
private String userName;
private List<SysUser> userList;
public static final String USER_STATUS_NORMAL = "0";
public static final int MAX_PAGE_SIZE = 1000;
private String userName;
private Long userId;
private Boolean isAdmin;
private Boolean hasPermission;
private List<SysUser> userList;
private Map<String, Object> userMap;
前端命名规范
user-list.vue
order-detail.vue
sys-user.ts
<script setup lang="ts" name="UserList">
export default {
name: 'OrderDetail'
}
const userName = ref('');
const handleQuery = () => {};
const MAX_UPLOAD_SIZE = 10 * 1024 * 1024;
const API_BASE_URL = 'http://api.example.com';
interface UserInfo {
userId: number;
userName: string;
}
type UserStatus = 0 | 1 | 2;
enum OrderStatus {
PENDING = 0,
PAID = 1,
SHIPPED = 2,
COMPLETED = 3
}
.user-list-container {}
.order-detail-header {}
const handleClick = () => {};
const handleSubmit = () => {};
const handleDelete = () => {};
代码注释规范
后端注释规范
@RestController
@RequestMapping("/system/order")
public class SysOrderController extends BaseController {
@GetMapping("/list")
public TableDataInfo list(SysOrderBo bo) {
startPage();
List<SysOrderVo> list = orderService.selectOrderList(bo);
return getDataTable(list);
}
@PostMapping
public AjaxResult add(@Validated @RequestBody SysOrderBo bo) {
return toAjax(orderService.insertOrder(bo));
}
}
前端注释规范
<script setup lang="ts" name="OrderList">
const getList = async () => {
loading.value = true;
try {
const res = await listOrder(queryParams);
orderList.value = res.rows;
total.value = res.total;
} finally {
loading.value = false;
}
};
const handleSelectionChange = (selection: SysOrderVo[]) => {
ids.value = selection.map(item => item.orderId);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
</script>
文件结构规范
后端模块结构
ruoyi-system/
├── src/main/java/com/ruoyi/system/
│ ├── controller/ # 控制器层
│ │ └── SysOrderController.java
│ ├── service/ # 服务接口层
│ │ ├── ISysOrderService.java
│ │ └── impl/ # 服务实现层
│ │ └── SysOrderServiceImpl.java
│ ├── mapper/ # 数据访问层
│ │ └── SysOrderMapper.java
│ ├── domain/ # 领域模型层
│ │ ├── SysOrder.java # 实体类
│ │ ├── bo/ # 业务对象
│ │ │ └── SysOrderBo.java
│ │ └── vo/ # 视图对象
│ │ └── SysOrderVo.java
│ └── enums/ # 枚举类
│ └── OrderStatusEnum.java
└── src/main/resources/
└── mapper/system/ # MyBatis XML
└── SysOrderMapper.xml
前端模块结构
ruoyi-ui/
├── src/
│ ├── views/system/order/ # 页面目录
│ │ ├── index.vue # 主页面
│ │ ├── components/ # 页面专用组件
│ │ │ ├── OrderDialog.vue
│ │ │ └── OrderDetail.vue
│ │ └── types.ts # 页面类型定义(可选)
│ ├── api/system/ # API接口
│ │ └── order/
│ │ ├── index.ts # API方法
│ │ └── types.ts # 接口类型定义
│ ├── components/ # 全局公共组件
│ │ ├── DictTag/
│ │ └── Pagination/
│ ├── utils/ # 工具函数
│ │ ├── request.ts # axios封装
│ │ └── validate.ts # 校验函数
│ └── stores/ # Pinia状态管理
│ └── modules/
│ └── order.ts
代码质量检查清单
提交代码前必须检查的项目
后端代码检查清单
前端代码检查清单
代码审查(Code Review)清单
在进行代码审查时,重点关注以下方面:
功能性审查
安全性审查
性能审查
可维护性审查
规范性审查
常见问题与解决方案
后端常见问题
问题1:Entity直接返回给前端
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
SysOrder order = orderService.getById(id);
return success(order);
}
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
SysOrderVo vo = orderService.selectOrderById(id);
return success(vo);
}
问题2:忘记添加事务注解
public boolean createOrder(SysOrder order) {
orderMapper.insert(order);
orderDetailMapper.insert(detail);
}
@Transactional(rollbackFor = Exception.class)
public boolean createOrder(SysOrder order) {
orderMapper.insert(order);
orderDetailMapper.insert(detail);
}
问题3:循环中执行数据库操作
for (Long id : ids) {
orderMapper.deleteById(id);
}
orderMapper.deleteBatchIds(Arrays.asList(ids));
问题4:查询全表数据
List<SysOrder> list = orderMapper.selectList(null);
startPage();
List<SysOrderVo> list = orderService.selectOrderList(bo);
前端常见问题
问题1:解构reactive对象丢失响应性
const state = reactive({ count: 0 });
const { count } = state;
count++;
const state = reactive({ count: 0 });
const { count } = toRefs(state);
count.value++;
问题2:直接修改props
const props = defineProps<{ visible: boolean }>();
props.visible = false;
const props = defineProps<{ visible: boolean }>();
const emit = defineEmits<{ (e: 'update:visible', value: boolean): void }>();
emit('update:visible', false);
问题3:ref忘记使用.value
const count = ref(0);
count++;
const count = ref(0);
count.value++;
问题4:表单提交不校验
const submitForm = () => {
addOrder(form);
};
const submitForm = async () => {
const valid = await formRef.value?.validate();
if (!valid) return;
addOrder(form);
};
总结
遵循本规范可以:
- ✅ 提高代码质量和可维护性
- ✅ 减少常见错误和安全隐患
- ✅ 提升开发效率和团队协作
- ✅ 保持代码风格统一
- ✅ 降低系统维护成本
记住:规范不是限制,而是让我们写出更好代码的指引!