代码规范
RuoYi-Plus 项目的代码编写规范,包含命名规范、注释规范、代码结构等最佳实践。
📋 基本原则
1. 可读性优先
代码首先是给人阅读的,其次才是给机器执行的。
java
// ✅ 好的代码 - 清晰易懂
public class UserService {
public boolean isValidUser(User user) {
return user != null
&& StringUtils.isNotBlank(user.getUsername())
&& StringUtils.isNotBlank(user.getEmail())
&& "0".equals(user.getStatus());
}
}
// ❌ 不好的代码 - 难以理解
public class UserService {
public boolean check(User u) {
return u != null && !u.getUsername().isEmpty()
&& !u.getEmail().isEmpty() && u.getStatus().equals("0");
}
}2. 一致性原则
在整个项目中保持命名、结构、风格的一致性。
java
// ✅ 一致的命名风格
public class UserController {
public R<PageResult<UserVo>> pageUsers(UserQuery query) { }
public R<UserVo> getUserById(Long id) { }
public R<Void> createUser(UserBo bo) { }
public R<Void> updateUser(UserBo bo) { }
public R<Void> deleteUser(Long id) { }
}
// ❌ 不一致的命名风格
public class UserController {
public R<PageResult<UserVo>> list(UserQuery query) { }
public R<UserVo> detail(Long id) { }
public R<Void> add(UserBo bo) { }
public R<Void> edit(UserBo bo) { }
public R<Void> remove(Long id) { }
}3. 简单性原则
优先选择简单、直接的解决方案。
java
// ✅ 简单直接
public String getUserStatusText(String status) {
return "0".equals(status) ? "正常" : "停用";
}
// ❌ 过度复杂
public String getUserStatusText(String status) {
Map<String, String> statusMap = new HashMap<>();
statusMap.put("0", "正常");
statusMap.put("1", "停用");
return statusMap.getOrDefault(status, "未知");
}🏷️ 命名规范
Java 命名
java
// 类名:大驼峰命名
public class UserController { }
public class DataTransferObject { }
// 方法名:小驼峰命名,动词开头
public void createUser() { }
public boolean isValidEmail() { }
public String getUserName() { }
// 变量名:小驼峰命名,名词
private String userName;
private boolean isActive;
private List<User> userList;
// 常量:全大写,下划线分隔
public static final String DEFAULT_PASSWORD = "123456";
public static final int MAX_RETRY_COUNT = 3;
// 包名:全小写,域名反写
com.ruoyi.system.controller
com.ruoyi.common.utils数据库命名
sql
-- 表名:小写,下划线分隔,前缀标识
sys_user -- 系统用户表
sys_role -- 系统角色表
b_product -- 业务产品表
log_operation -- 操作日志表
-- 字段名:小写,下划线分隔
user_id -- 用户ID
user_name -- 用户名
create_time -- 创建时间
update_time -- 更新时间
-- 索引名:表名_字段名_idx
idx_sys_user_username
idx_sys_user_email
idx_sys_user_create_time前端命名
typescript
// 组件名:大驼峰命名
const UserList = defineComponent({ })
const DataTable = defineComponent({ })
// 变量/函数名:小驼峰命名
const userName = ref('')
const isLoading = ref(false)
const fetchUserList = () => { }
// 常量:全大写,下划线分隔
const API_BASE_URL = '/api'
const DEFAULT_PAGE_SIZE = 10
// 文件名:短横线分隔
user-list.vue
data-table.vue
user-detail.vue移动端命名
typescript
// 页面目录:短横线分隔
pages/user-list/user-list.vue
pages/product-detail/product-detail.vue
// 组件:大驼峰命名
components/UserCard/UserCard.vue
components/ProductItem/ProductItem.vue
// 样式类:短横线分隔
.user-card { }
.product-item { }
.btn-primary { }
// CSS变量:双短横线开头
--primary-color: #409eff;
--border-radius: 4px;📝 注释规范
Java 注释
java
/**
* 用户服务接口
*
* @author ruoyi
* @date 2023-01-01
*/
public interface IUserService {
/**
* 根据用户ID查询用户信息
*
* @param userId 用户ID
* @return 用户信息
* @throws ServiceException 当用户不存在时抛出异常
*/
UserVo selectUserById(Long userId);
/**
* 分页查询用户列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 分页结果
*/
PageResult<UserVo> queryPageList(UserBo bo, PageQuery pageQuery);
/**
* 新增用户
*
* @param bo 用户信息
* @return 用户ID
*/
Long insertUser(UserBo bo);
}
/**
* 用户服务实现类
*/
@Service
public class UserServiceImpl implements IUserService {
@Resource
private SysUserMapper baseMapper;
@Override
public UserVo selectUserById(Long userId) {
// 参数校验
Assert.notNull(userId, "用户ID不能为空");
// 查询用户信息
UserVo user = baseMapper.selectVoById(userId);
if (ObjectUtil.isNull(user)) {
throw new ServiceException("用户不存在");
}
// 查询用户角色信息
List<SysRole> roles = roleMapper.selectRolesByUserId(userId);
user.setRoles(roles);
return user;
}
/**
* 验证用户名是否唯一
*
* @param user 用户信息
* @return true=唯一 false=不唯一
*/
private boolean checkUserNameUnique(SysUser user) {
Long userId = ObjectUtil.isNull(user.getUserId()) ? -1L : user.getUserId();
SysUser info = baseMapper.checkUserNameUnique(user.getUserName());
return ObjectUtil.isNull(info) || info.getUserId().equals(userId);
}
}前端注释
typescript
/**
* 用户管理页面
*
* @description 提供用户的增删改查功能
* @author ruoyi
* @date 2023-01-01
*/
export default defineComponent({
name: 'UserList',
setup() {
const { proxy } = getCurrentInstance() as ComponentInternalInstance
// 查询参数
const queryParams = reactive<UserQuery>({
pageNum: 1,
pageSize: 10,
userName: '',
status: ''
})
/**
* 查询用户列表
*/
const getList = async () => {
loading.value = true
try {
const response = await listUser(queryParams)
userList.value = response.data.records
total.value = response.data.total
} catch (error) {
console.error('查询用户列表失败:', error)
} finally {
loading.value = false
}
}
/**
* 处理查询
*/
const handleQuery = () => {
queryParams.pageNum = 1
getList()
}
/**
* 重置查询条件
*/
const resetQuery = () => {
proxy?.resetForm('queryFormRef')
handleQuery()
}
return {
queryParams,
getList,
handleQuery,
resetQuery
}
}
})移动端注释
vue
<template>
<!-- 用户列表页面 -->
<view class="user-list">
<!-- 搜索栏 -->
<wd-search
v-model="searchValue"
placeholder="搜索用户"
@search="handleSearch"
/>
<!-- 用户列表 -->
<wd-cell-group>
<wd-cell
v-for="user in userList"
:key="user.id"
:title="user.nickname"
:label="user.username"
is-link
@click="handleUserClick(user)"
/>
</wd-cell-group>
</view>
</template>
<script setup lang="ts">
/**
* 用户列表页面
*
* @description 展示用户列表,支持搜索和查看详情
*/
interface UserItem {
id: number
username: string
nickname: string
avatar?: string
}
// 搜索关键词
const searchValue = ref('')
// 用户列表
const userList = ref<UserItem[]>([])
/**
* 处理搜索
* @param value 搜索关键词
*/
const handleSearch = (value: string) => {
console.log('搜索用户:', value)
// TODO: 实现搜索逻辑
}
/**
* 处理用户点击
* @param user 用户信息
*/
const handleUserClick = (user: UserItem) => {
uni.navigateTo({
url: `/pages/user/detail?id=${user.id}`
})
}
// 页面加载时获取用户列表
onMounted(() => {
// TODO: 获取用户列表
})
</script>🏗️ 代码结构
控制器结构
java
/**
* 用户管理控制器
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/user")
public class SysUserController extends BaseController {
private final IUserService userService;
/**
* 获取用户列表
*/
@SaCheckPermission("system:user:list")
@GetMapping("/list")
public TableDataInfo<UserVo> list(UserBo user, PageQuery pageQuery) {
return userService.queryPageList(user, pageQuery);
}
/**
* 根据用户编号获取详细信息
*/
@SaCheckPermission("system:user:query")
@GetMapping(value = {"/", "/{userId}"})
public R<UserVo> getInfo(@PathVariable(value = "userId", required = false) Long userId) {
userService.checkUserDataScope(userId);
return R.ok(userService.selectUserById(userId));
}
/**
* 新增用户
*/
@SaCheckPermission("system:user:add")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping
public R<Void> add(@Validated(AddGroup.class) @RequestBody UserBo user) {
if (!userService.checkUserNameUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
}
if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
}
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
user.setCreateBy(getUsername());
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
return toAjax(userService.insertUser(user));
}
/**
* 修改用户
*/
@SaCheckPermission("system:user:edit")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
public R<Void> edit(@Validated(EditGroup.class) @RequestBody UserBo user) {
userService.checkUserAllowed(user.getUserId());
userService.checkUserDataScope(user.getUserId());
if (!userService.checkUserNameUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
}
if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
}
if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
user.setUpdateBy(getUsername());
return toAjax(userService.updateUser(user));
}
/**
* 删除用户
*/
@SaCheckPermission("system:user:remove")
@Log(title = "用户管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{userIds}")
public R<Void> remove(@PathVariable Long[] userIds) {
if (ArrayUtil.contains(userIds, getUserId())) {
return R.fail("当前用户不能删除");
}
return toAjax(userService.deleteUserByIds(userIds));
}
}服务层结构
java
/**
* 用户服务实现类
*/
@RequiredArgsConstructor
@Service
public class UserServiceImpl implements IUserService {
private final SysUserMapper baseMapper;
private final SysRoleMapper roleMapper;
private final SysPostMapper postMapper;
/**
* 根据条件分页查询用户列表
*/
@Override
public TableDataInfo<UserVo> queryPageList(UserBo user, PageQuery pageQuery) {
LambdaQueryWrapper<SysUser> lqw = buildQueryWrapper(user);
Page<UserVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 根据用户ID查询用户
*/
@Override
public UserVo selectUserById(Long userId) {
return baseMapper.selectVoById(userId);
}
/**
* 校验用户名称是否唯一
*/
@Override
public boolean checkUserNameUnique(UserBo user) {
boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUserName, user.getUserName())
.ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
return !exist;
}
/**
* 校验手机号码是否唯一
*/
@Override
public boolean checkPhoneUnique(UserBo user) {
boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getPhonenumber, user.getPhonenumber())
.ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
return !exist;
}
/**
* 校验email是否唯一
*/
@Override
public boolean checkEmailUnique(UserBo user) {
boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getEmail, user.getEmail())
.ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
return !exist;
}
/**
* 校验用户是否允许操作
*/
@Override
public void checkUserAllowed(Long userId) {
if (ObjectUtil.isNotNull(userId) && LoginHelper.isAdmin(userId)) {
throw new ServiceException("不允许操作管理员用户");
}
}
/**
* 校验用户是否有数据权限
*/
@Override
public void checkUserDataScope(Long userId) {
if (!LoginHelper.isAdmin()) {
SysUser user = new SysUser();
user.setUserId(userId);
List<SysUser> users = SpringUtils.getAopProxy(this).selectUserList(user);
if (CollUtil.isEmpty(users)) {
throw new ServiceException("没有权限访问用户数据!");
}
}
}
/**
* 新增保存用户信息
*/
@Override
public int insertUser(UserBo user) {
SysUser sysUser = BeanUtil.toBean(user, SysUser.class);
return baseMapper.insert(sysUser);
}
/**
* 修改保存用户信息
*/
@Override
public int updateUser(UserBo user) {
SysUser sysUser = BeanUtil.toBean(user, SysUser.class);
return baseMapper.updateById(sysUser);
}
/**
* 批量删除用户信息
*/
@Override
public int deleteUserByIds(Long[] userIds) {
for (Long userId : userIds) {
checkUserAllowed(userId);
checkUserDataScope(userId);
}
return baseMapper.deleteBatchIds(Arrays.asList(userIds));
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper<SysUser> buildQueryWrapper(UserBo user) {
Map<String, Object> params = user.getParams();
LambdaQueryWrapper<SysUser> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(user.getUserName()), SysUser::getUserName, user.getUserName());
lqw.like(StringUtils.isNotBlank(user.getNickName()), SysUser::getNickName, user.getNickName());
lqw.eq(StringUtils.isNotBlank(user.getStatus()), SysUser::getStatus, user.getStatus());
lqw.eq(StringUtils.isNotBlank(user.getPhonenumber()), SysUser::getPhonenumber, user.getPhonenumber());
lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
SysUser::getCreateTime, params.get("beginTime"), params.get("endTime"));
lqw.eq(ObjectUtil.isNotNull(user.getDeptId()), SysUser::getDeptId, user.getDeptId());
return lqw;
}
}✅ 最佳实践
1. 异常处理
java
// ✅ 好的异常处理
@Service
public class UserServiceImpl {
public UserVo getUserById(Long userId) {
// 参数校验
if (ObjectUtil.isNull(userId)) {
throw new ServiceException("用户ID不能为空");
}
// 业务逻辑
UserVo user = baseMapper.selectVoById(userId);
if (ObjectUtil.isNull(user)) {
throw new ServiceException("用户不存在");
}
return user;
}
}
// ❌ 不好的异常处理
@Service
public class UserServiceImpl {
public UserVo getUserById(Long userId) {
try {
return baseMapper.selectVoById(userId);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}2. 日志记录
java
// ✅ 好的日志记录
@Slf4j
@Service
public class UserServiceImpl {
public boolean resetPassword(Long userId, String newPassword) {
log.info("开始重置用户密码, userId: {}", userId);
try {
// 业务逻辑
SysUser user = baseMapper.selectById(userId);
if (ObjectUtil.isNull(user)) {
log.warn("重置密码失败,用户不存在, userId: {}", userId);
throw new ServiceException("用户不存在");
}
user.setPassword(SecurityUtils.encryptPassword(newPassword));
int result = baseMapper.updateById(user);
log.info("用户密码重置成功, userId: {}", userId);
return result > 0;
} catch (Exception e) {
log.error("重置用户密码异常, userId: {}", userId, e);
throw new ServiceException("重置密码失败");
}
}
}
// ❌ 不好的日志记录
@Service
public class UserServiceImpl {
public boolean resetPassword(Long userId, String newPassword) {
System.out.println("重置密码: " + userId);
SysUser user = baseMapper.selectById(userId);
user.setPassword(SecurityUtils.encryptPassword(newPassword));
return baseMapper.updateById(user) > 0;
}
}3. 参数校验
java
// ✅ 好的参数校验
@RestController
public class UserController {
@PostMapping("/user")
public R<Void> createUser(@Validated @RequestBody UserBo user) {
return R.ok(userService.createUser(user));
}
}
@Data
public class UserBo {
@NotBlank(message = "用户名不能为空")
@Length(min = 2, max = 20, message = "用户名长度必须在2-20之间")
private String userName;
@NotBlank(message = "密码不能为空")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$",
message = "密码必须包含大小写字母和数字,长度至少8位")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
// ❌ 不好的参数校验
@RestController
public class UserController {
@PostMapping("/user")
public R<Void> createUser(@RequestBody UserBo user) {
if (user.getUserName() == null || user.getUserName().isEmpty()) {
return R.fail("用户名不能为空");
}
// ... 更多if判断
return R.ok(userService.createUser(user));
}
}4. 事务管理
java
// ✅ 好的事务管理
@Service
@Transactional(readOnly = true)
public class UserServiceImpl {
@Transactional(rollbackFor = Exception.class)
public boolean createUserWithRoles(UserBo userBo, List<Long> roleIds) {
// 创建用户
SysUser user = BeanUtil.toBean(userBo, SysUser.class);
int userResult = baseMapper.insert(user);
// 分配角色
if (CollUtil.isNotEmpty(roleIds)) {
List<SysUserRole> userRoles = roleIds.stream()
.map(roleId -> new SysUserRole(user.getUserId(), roleId))
.collect(Collectors.toList());
userRoleMapper.insertBatch(userRoles);
}
return userResult > 0;
}
// 只读方法不需要事务
public UserVo getUserById(Long userId) {
return baseMapper.selectVoById(userId);
}
}
// ❌ 不好的事务管理
@Service
public class UserServiceImpl {
// 所有方法都加事务
@Transactional
public UserVo getUserById(Long userId) {
return baseMapper.selectVoById(userId);
}
// 没有指定回滚条件
@Transactional
public boolean createUser(UserBo userBo) {
// 可能出现检查异常但不会回滚
}
}代码规范是保证项目质量的基础,通过统一的规范可以提高代码的可读性、可维护性和团队协作效率。
🔒 安全编码规范
SQL 注入防护
java
// ✅ 使用参数化查询
@Override
public List<SysUser> selectUserList(SysUser user) {
LambdaQueryWrapper<SysUser> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(user.getUserName()),
SysUser::getUserName, user.getUserName());
return baseMapper.selectList(lqw);
}
// ❌ 拼接SQL(危险)
@Override
public List<SysUser> selectUserList(String userName) {
String sql = "SELECT * FROM sys_user WHERE user_name = '" + userName + "'";
return jdbcTemplate.query(sql, new UserRowMapper());
}XSS 防护
java
// ✅ 使用 HtmlUtil 转义
@PostMapping("/comment")
public R<Void> addComment(@RequestBody CommentBo bo) {
// 对用户输入进行转义
bo.setContent(HtmlUtil.escape(bo.getContent()));
return R.ok(commentService.add(bo));
}
// 前端同样需要转义
<template>
<!-- 使用 v-text 代替 v-html -->
<div v-text="userInput"></div>
<!-- 如需渲染HTML,使用DOMPurify -->
<div v-html="sanitizedHtml"></div>
</template>
<script setup lang="ts">
import DOMPurify from 'dompurify'
const sanitizedHtml = computed(() =>
DOMPurify.sanitize(rawHtml.value)
)
</script>敏感信息处理
java
// ✅ 敏感信息脱敏
@Data
public class UserVo {
private Long userId;
private String userName;
@Sensitive(strategy = SensitiveStrategy.PHONE)
private String phone; // 138****8888
@Sensitive(strategy = SensitiveStrategy.EMAIL)
private String email; // t***@example.com
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
private String idCard; // 110***********1234
@JsonIgnore
private String password; // 不返回密码
}
// ✅ 密码加密存储
public void createUser(UserBo user) {
String encodedPassword = BCrypt.hashpw(user.getPassword(), BCrypt.gensalt());
user.setPassword(encodedPassword);
}⚡ 性能编码规范
数据库查询优化
java
// ✅ 按需查询字段
@Override
public List<UserVo> selectUserList() {
return baseMapper.selectVoList(Wrappers.<SysUser>lambdaQuery()
.select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName)
);
}
// ✅ 分页查询大数据量
@Override
public TableDataInfo<UserVo> queryPageList(UserBo bo, PageQuery pageQuery) {
Page<UserVo> page = baseMapper.selectVoPage(pageQuery.build(), buildQueryWrapper(bo));
return TableDataInfo.build(page);
}
// ✅ 批量操作
@Override
@Transactional(rollbackFor = Exception.class)
public boolean insertBatch(List<UserBo> users) {
List<SysUser> list = BeanUtil.copyToList(users, SysUser.class);
return baseMapper.insertBatch(list); // 批量插入
}
// ❌ 避免在循环中查询
public void badExample(List<Long> userIds) {
for (Long userId : userIds) {
SysUser user = baseMapper.selectById(userId); // N+1 问题
}
}
// ✅ 改用批量查询
public void goodExample(List<Long> userIds) {
List<SysUser> users = baseMapper.selectBatchIds(userIds);
}缓存使用规范
java
// ✅ 合理使用缓存
@Cacheable(cacheNames = "user", key = "#userId")
public UserVo getUserById(Long userId) {
return baseMapper.selectVoById(userId);
}
@CacheEvict(cacheNames = "user", key = "#user.userId")
public void updateUser(UserBo user) {
baseMapper.updateById(BeanUtil.toBean(user, SysUser.class));
}
// ✅ 缓存空值防止穿透
@Cacheable(cacheNames = "user", key = "#userId", unless = "#result == null")
public UserVo getUserById(Long userId) {
return baseMapper.selectVoById(userId);
}集合操作优化
java
// ✅ 使用 Stream 高效处理
List<String> userNames = users.stream()
.filter(user -> "0".equals(user.getStatus()))
.map(SysUser::getUserName)
.collect(Collectors.toList());
// ✅ 使用 Map 提高查找效率
Map<Long, SysUser> userMap = users.stream()
.collect(Collectors.toMap(SysUser::getUserId, Function.identity()));
SysUser user = userMap.get(targetUserId);
// ❌ 避免嵌套循环查找
for (Order order : orders) {
for (User user : users) {
if (order.getUserId().equals(user.getId())) {
// 效率低
}
}
}🧪 测试代码规范
单元测试结构
java
@SpringBootTest
class UserServiceTest {
@Autowired
private IUserService userService;
@MockBean
private SysUserMapper userMapper;
/**
* 测试方法命名:方法名_场景_期望结果
*/
@Test
@DisplayName("根据ID查询用户 - 用户存在 - 返回用户信息")
void selectUserById_WhenUserExists_ReturnsUser() {
// Arrange - 准备数据
Long userId = 1L;
SysUser mockUser = new SysUser();
mockUser.setUserId(userId);
mockUser.setUserName("testUser");
when(userMapper.selectVoById(userId))
.thenReturn(BeanUtil.toBean(mockUser, UserVo.class));
// Act - 执行测试
UserVo result = userService.selectUserById(userId);
// Assert - 验证结果
assertNotNull(result);
assertEquals("testUser", result.getUserName());
verify(userMapper).selectVoById(userId);
}
@Test
@DisplayName("根据ID查询用户 - 用户不存在 - 抛出异常")
void selectUserById_WhenUserNotExists_ThrowsException() {
// Arrange
Long userId = 999L;
when(userMapper.selectVoById(userId)).thenReturn(null);
// Act & Assert
assertThrows(ServiceException.class, () ->
userService.selectUserById(userId)
);
}
}前端测试规范
typescript
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import UserList from '@/views/system/user/index.vue'
describe('UserList.vue', () => {
it('should render user list correctly', async () => {
const wrapper = mount(UserList)
// 等待异步数据加载
await wrapper.vm.$nextTick()
// 验证表格渲染
expect(wrapper.find('.el-table').exists()).toBe(true)
})
it('should call delete API when clicking delete button', async () => {
const deleteSpy = vi.spyOn(userApi, 'deleteUser')
const wrapper = mount(UserList)
// 模拟点击删除
await wrapper.find('.btn-delete').trigger('click')
await wrapper.find('.confirm-btn').trigger('click')
expect(deleteSpy).toHaveBeenCalled()
})
})📋 代码审查清单
功能正确性
- [ ] 代码逻辑是否正确实现了需求
- [ ] 边界条件是否处理(空值、空集合、极限值)
- [ ] 异常情况是否妥善处理
- [ ] 并发场景是否考虑
代码质量
- [ ] 命名是否清晰、有意义
- [ ] 方法长度是否适中(建议不超过50行)
- [ ] 类职责是否单一
- [ ] 是否有重复代码需要提取
- [ ] 注释是否必要且准确
性能考虑
- [ ] 是否有N+1查询问题
- [ ] 大数据量是否使用分页
- [ ] 是否合理使用缓存
- [ ] 是否有不必要的数据库操作
安全性
- [ ] 用户输入是否校验
- [ ] 是否防止SQL注入
- [ ] 敏感信息是否脱敏
- [ ] 权限是否正确控制
可维护性
- [ ] 代码是否易于理解
- [ ] 配置是否外部化
- [ ] 是否有适当的日志记录
- [ ] 错误信息是否有助于排查问题
🎨 代码格式规范
Java 代码格式
java
// ✅ 推荐的代码格式
public class UserServiceImpl implements IUserService {
private final SysUserMapper baseMapper;
@Override
public UserVo selectUserById(Long userId) {
// 空行分隔逻辑块
if (ObjectUtil.isNull(userId)) {
throw new ServiceException("用户ID不能为空");
}
UserVo user = baseMapper.selectVoById(userId);
// 返回前空一行
return user;
}
@Override
public boolean updateUser(UserBo user) {
// 链式调用换行
return lambdaUpdate()
.set(SysUser::getNickName, user.getNickName())
.set(SysUser::getEmail, user.getEmail())
.set(SysUser::getPhone, user.getPhone())
.eq(SysUser::getUserId, user.getUserId())
.update();
}
}TypeScript 代码格式
typescript
// ✅ 推荐的代码格式
export function useUserList() {
// 状态定义
const loading = ref(false)
const userList = ref<UserVO[]>([])
const total = ref(0)
// 查询参数
const queryParams = reactive<UserQuery>({
pageNum: 1,
pageSize: 10,
userName: '',
status: ''
})
// 方法定义
const getList = async () => {
loading.value = true
try {
const { data } = await listUser(queryParams)
userList.value = data.records
total.value = data.total
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.pageNum = 1
getList()
}
// 返回值
return {
loading,
userList,
total,
queryParams,
getList,
handleQuery
}
}Vue 模板格式
vue
<template>
<div class="user-list">
<!-- 搜索区域 -->
<el-form :model="queryParams" inline>
<el-form-item label="用户名">
<el-input
v-model="queryParams.userName"
placeholder="请输入用户名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
搜索
</el-button>
<el-button @click="resetQuery">
重置
</el-button>
</el-form-item>
</el-form>
<!-- 表格区域 -->
<el-table
v-loading="loading"
:data="userList"
border
>
<el-table-column prop="userName" label="用户名" />
<el-table-column prop="nickName" label="昵称" />
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
{{ row.status === '0' ? '正常' : '停用' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<pagination
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</div>
</template>📝 TODO 和 FIXME 规范
java
// TODO: 描述需要完成的功能
// TODO(author): 待优化的批量删除逻辑,需要添加事务控制
// FIXME: 描述需要修复的问题
// FIXME(author): 这里存在并发问题,需要添加锁机制
// NOTE: 描述需要注意的事项
// NOTE: 这个方法仅供内部使用,不要在Controller中直接调用
// HACK: 描述临时解决方案
// HACK: 由于第三方API限制,这里使用了轮询方式
// DEPRECATED: 标记废弃代码
// DEPRECATED: 该方法将在2.0版本移除,请使用 newMethod() 代替