良好的代码规范是保证代码质量、提高开发效率、减少维护成本的重要手段。本文档总结了 Tagtag Starter 项目的代码规范和最佳实践,包括 Java 后端和 TypeScript 前端的编码规范、命名约定和代码审查标准。
无论使用哪种编程语言,以下通用原则都应遵循:
Tagtag Starter 后端使用 Java 语言开发,遵循以下代码规范:
UserService、PostControllergetUserById、savePostuserName、pageSizeMAX_PAGE_SIZE、DEFAULT_STATUSdev.tagtag.common.utilUserService、PostMapper实际项目示例:
// 类名示例
@Service
public class UserServiceImpl implements UserService { }
@RestController
@RequestMapping("/users")
public class UserController { }
// 方法名示例
public UserDTO getUserById(Long userId) { }
public PageResult<UserDTO> pageUsers(UserQuery query) { }
public void createUser(UserDTO user) { }
public void updateUser(UserDTO user) { }
public void deleteUser(Long userId) { }
// 常量示例
private static final int MAX_PAGE_SIZE = 100;
private static final String DEFAULT_AVATAR = "/default-avatar.png";
private static final Integer STATUS_ACTIVE = 1;
{ 与语句同一行,右括号 } 单独一行a = b + cmethod(a, b, c)method(a, b) 而非 method( a, b )/**
* 用户服务类,负责用户相关业务逻辑
*
* @author Tagtag Starter Team
* @since 2024-01-01
*/
@Service
public class UserService {
// 类实现
}
/**
* 根据用户 ID 获取用户信息
*
* @param userId 用户 ID
* @return 用户信息
* @throws BusinessException 如果用户不存在
*/
public UserDTO getUserById(Long userId) {
// 方法实现
}
// 注释,说明复杂逻辑或特殊处理// 处理特殊情况:当用户未设置头像时,使用默认头像
if (user.getAvatar() == null) {
user.setAvatar(DEFAULT_AVATAR);
}
@Data、@NoArgsConstructor、@AllArgsConstructor 等注解简化 POJO 类@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
private Long id;
private String username;
private String email;
private Integer status;
}
private static final int MAX_RETRY = 3// 不推荐
if (user.getStatus() == 1) { }
// 推荐
private static final Integer STATUS_ACTIVE = 1;
if (user.getStatus().equals(STATUS_ACTIVE)) { }
// 不推荐
User user = userMapper.selectById(userId);
if (user != null) {
return user.getName();
}
return null;
// 推荐
return Optional.ofNullable(userMapper.selectById(userId))
.map(User::getName)
.orElse(null);
try (InputStream is = new FileInputStream(file)) {
// 处理文件
} catch (IOException e) {
log.error("文件读取失败", e);
}
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final RoleService roleService;
@Override
public UserDTO getUserById(Long userId) {
User user = userMapper.selectById(userId);
return BeanUtil.copyProperties(user, UserDTO.class);
}
}
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
@Operation(summary = "获取用户详情")
@PreAuthorize("@ss.hasPermission('system:user:query')")
public Result<UserDTO> getById(@PathVariable Long id) {
return Result.ok(userService.getById(id));
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
log.error("业务异常", e);
return Result.fail(e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.fail("系统错误,请联系管理员");
}
}
@PostMapping
@Operation(summary = "创建用户")
public Result<Void> create(@Valid @RequestBody UserDTO dto) {
userService.create(dto);
return Result.okMsg("创建成功");
}
// DTO 定义
@Data
public class UserDTO {
@NotBlank(message = "用户名不能为空")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@NotNull(message = "状态不能为空")
private Integer status;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void createUserWithRoles(UserDTO user, List<Long> roleIds) {
// 创建用户
userService.create(user);
// 分配角色
if (CollectionUtils.isNotEmpty(roleIds)) {
userRoleService.assignRoles(user.getId(), roleIds);
}
}
// 不推荐
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq("username", username);
// 推荐
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, username);
@Override
public PageResult<UserDTO> page(UserQuery query, PageQuery pageQuery) {
IPage<UserVO> page = new Page<>(pageQuery.getPage(), pageQuery.getSize());
IPage<UserVO> result = userMapper.selectPage(page, query);
return PageResult.of(
result.getRecords().stream()
.map(vo -> BeanUtil.copyProperties(vo, UserDTO.class))
.toList(),
result.getTotal()
);
}
@Data
@TableName("sys_user")
public class User {
@TableId
private Long id;
private String username;
@TableLogic
private Integer deleted;
}
Tagtag Starter 前端使用 TypeScript 语言开发,遵循以下代码规范:
PostList、FormModaluserName、isLoadinggetUserInfo、handleSubmitMAX_PAGE_SIZE、API_BASE_URLUserDTO、PostQueryUserService、PostApi实际项目示例:
// 组件名示例
export default defineComponent({
name: 'UserList'
});
// 变量名示例
const userName = ref('');
const isLoading = ref(false);
const userList = ref<UserDTO[]>([]);
// 函数名示例
function getUserInfo() { }
function handleSubmit() { }
function handleDelete() { }
// 常量名示例
const MAX_PAGE_SIZE = 100;
const API_BASE_URL = '/api';
const STATUS_ACTIVE = 1;
// 类型名示例
interface UserDTO {
id?: number;
username: string;
email?: string;
status?: number;
}
interface PostQuery {
title?: string;
status?: number;
page: number;
size: number;
}
{ 与语句同一行,右括号 } 单独一行a = b + cmethod(a, b, c)method(a, b) 而非 method( a, b )/**
* 文章列表组件,展示文章列表并提供筛选、排序功能
*
* @component
* @param {Object} props - 组件属性
* @param {Function} onEdit - 编辑文章回调函数
* @param {Function} onDelete - 删除文章回调函数
*/
export default defineComponent({
// 组件实现
});
/**
* 获取文章列表
*
* @param {PostQuery} params - 查询参数
* @returns {Promise<PageResult<PostDTO>>} 文章列表
*/
export function getPostList(params: PostQuery): Promise<PageResult<PostDTO>> {
// 函数实现
}
// 注释,说明复杂逻辑或特殊处理// 处理特殊情况:当文章状态为 0 时,显示为草稿
const statusText = status === 1 ? '已发布' : '草稿';
strict: true 配置,提高类型安全性// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
// 不推荐
function getUser(id: any): any {
return request.get({ url: `/users/${id}` });
}
// 推荐
function getUser(id: number): Promise<UserDTO> {
return request.get<UserDTO>({ url: `/users/${id}` });
}
interface UserDTO {
id?: number;
username: string;
email?: string;
status?: number;
createTime?: string;
}
interface PageResult<T> {
records: T[];
total: number;
current: number;
size: number;
}
interface UserQuery {
username?: string;
status?: number;
page: number;
size: number;
}
// 不推荐
let userName = 'admin';
let status = 1;
// 推荐
const userName = 'admin';
const status = 1;
// 需要重新赋值时使用 let
let currentPage = 1;
currentPage++;
// 不推荐
const handleClick = function() {
console.log('clicked');
};
// 推荐
const handleClick = () => {
console.log('clicked');
};
// 不推荐
const user = response.data;
const id = user.id;
const name = user.name;
// 推荐
const { id, name } = response.data;
// 数组解构
const [first, second] = [1, 2, 3];
// 不推荐
const url = '/api/users/' + userId + '/roles/' + roleId;
// 推荐
const url = `/api/users/${userId}/roles/${roleId}`;
// 不推荐
const userName = user && user.profile && user.profile.name;
// 推荐
const userName = user?.profile?.name;
// 不推荐
const userName = user.name || 'Unknown';
// 推荐
const userName = user.name ?? 'Unknown';
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { getUserList } from '@/api/modules/system/user';
const userList = ref<UserDTO[]>([]);
const loading = ref(false);
const activeUsers = computed(() =>
userList.value.filter(user => user.status === 1)
);
async function loadData() {
loading.value = true;
try {
const result = await getUserList({ page: 1, size: 10 });
userList.value = result.records;
} finally {
loading.value = false;
}
}
onMounted(() => {
loadData();
});
</script>
const props = defineProps<{
title: string;
visible: boolean;
formData?: UserDTO;
}>();
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
(e: 'save', data: UserDTO): void;
(e: 'cancel'): void;
}>();
// 使用 props
console.log(props.title);
// 触发事件
emit('save', formData);
// watch:监听特定数据源
watch(
() => props.visible,
(newVal) => {
if (newVal) {
// 弹窗打开时的处理
}
}
);
// watchEffect:自动追踪依赖
watchEffect(() => {
console.log(`用户列表长度: ${userList.value.length}`);
});
// 监听多个数据源
watch([page, size], ([newPage, newSize]) => {
loadData();
});
// 父组件
provide('userService', {
getUserList,
createUser,
updateUser
});
// 子组件
const userService = inject('userService');
await userService?.getUserList({ page: 1, size: 10 });
<template>
<Teleport to="body">
<Modal v-model:open="visible" title="用户信息">
<!-- 模态框内容 -->
</Modal>
</Teleport>
</template>
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>
import { useTable } from '@vben/plugins/vxe-table';
const [Grid, gridApi] = useTable({
api: getUserList,
columns: [
{ type: 'checkbox', width: 50 },
{ field: 'username', title: '用户名' },
{ field: 'email', title: '邮箱' },
{ field: 'status', title: '状态', slots: { default: 'status' } },
{ title: '操作', slots: { default: 'action' }, width: 150 }
],
formConfig: {
items: [
{ field: 'username', label: '用户名', component: 'Input' },
{ field: 'status', label: '状态', component: 'Select' }
]
}
});
// stores/user.ts
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
currentUser: null as UserDTO | null,
token: ''
}),
getters: {
isLoggedIn: (state) => !!state.token,
userName: (state) => state.currentUser?.username || ''
},
actions: {
async login(username: string, password: string) {
const result = await login({ username, password });
this.token = result.token;
this.currentUser = result.user;
},
logout() {
this.token = '';
this.currentUser = null;
}
}
});
// 使用 store
const userStore = useUserStore();
await userStore.login('admin', '123456');
console.log(userStore.userName);
// composables/useTableData.ts
export function useTableData<T>(api: Function) {
const data = ref<T[]>([]);
const loading = ref(false);
const total = ref(0);
async function loadData(params: any) {
loading.value = true;
try {
const result = await api(params);
data.value = result.records;
total.value = result.total;
} finally {
loading.value = false;
}
}
return { data, loading, total, loadData };
}
// 使用 composable
const { data, loading, total, loadData } = useTableData<UserDTO>(getUserList);
dev.tagtag.common.util@/api/modules/system/postuser-management、post-listUserService、PostControllerUserDTO、PostQueryUserRepository、PostServiceget 或 find 前缀,例如 getUserById、findPostssave 或 create 前缀,例如 saveUser、createPostupdate 前缀,例如 updateUser、updatePostdelete 前缀,例如 deleteUser、deletePostlist 或 page 前缀,例如 listUsers、pagePostshandle 前缀,例如 handleSubmit、handleDelete实际项目示例:
// 后端方法命名示例
public interface UserService {
UserDTO getUserById(Long userId);
PageResult<UserDTO> pageUsers(UserQuery query);
List<UserDTO> listActiveUsers();
void createUser(UserDTO user);
void updateUser(UserDTO user);
void deleteUser(Long userId);
void batchDeleteUsers(List<Long> ids);
void updateUserStatus(Long userId, Integer status);
boolean existsByUsername(String username);
}
// 前端方法命名示例
export function getUserById(id: number): Promise<UserDTO> { }
export function getUserList(params: UserQuery): Promise<PageResult<UserDTO>> { }
export function createUser(data: UserDTO): Promise<void> { }
export function updateUser(data: UserDTO): Promise<void> { }
export function deleteUser(id: number): Promise<void> { }
export function batchDeleteUsers(ids: number[]): Promise<void> { }
export function updateUserStatus(id: number, status: number): Promise<void> { }
// 事件处理方法命名示例
function handleSubmit() { }
function handleEdit(row: UserDTO) { }
function handleDelete(id: number) { }
function handleBatchDelete(ids: number[]) { }
function handleStatusChange(id: number, status: number) { }
function handlePageChange(page: number) { }
function handleSearch() { }
function handleReset() { }
is 或 has 前缀,例如 isLoading、hasPermissionusers、postsuserList、postMapMAX_PAGE_SIZE、DEFAULT_AVATAR实际项目示例:
// 后端变量命名示例
private boolean isLoading;
private boolean hasPermission;
private boolean isDeleted;
private boolean isActive;
private List<UserDTO> users;
private List<PostDTO> posts;
private List<RoleDTO> roles;
private Map<Long, UserDTO> userMap;
private Map<String, RoleDTO> roleMap;
private static final int MAX_PAGE_SIZE = 100;
private static final int DEFAULT_PAGE_SIZE = 10;
private static final String DEFAULT_AVATAR = "/default-avatar.png";
private static final Integer STATUS_ACTIVE = 1;
private static final Integer STATUS_INACTIVE = 0;
// 前端变量命名示例
const isLoading = ref(false);
const hasPermission = ref(false);
const isModalVisible = ref(false);
const isFormValid = ref(false);
const users = ref<UserDTO[]>([]);
const posts = ref<PostDTO[]>([]);
const roles = ref<RoleDTO[]>([]);
const selectedUserIds = ref<number[]>([]);
const currentPage = ref(1);
const pageSize = ref(10);
const MAX_PAGE_SIZE = 100;
const DEFAULT_PAGE_SIZE = 10;
const API_BASE_URL = '/api';
const STATUS_ACTIVE = 1;
const STATUS_INACTIVE = 0;
代码审查是保证代码质量的重要环节,以下是 Tagtag Starter 项目的代码审查标准:
实际项目示例:
// 必须修复:SQL 注入漏洞
// 不推荐
String sql = "SELECT * FROM user WHERE username = '" + username + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// 推荐
String sql = "SELECT * FROM user WHERE username = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();
// 必须修复:性能问题 - N+1 查询
// 不推荐
List<User> users = userMapper.selectList(null);
for (User user : users) {
List<Role> roles = roleMapper.selectByUserId(user.getId());
user.setRoles(roles);
}
// 推荐
List<UserVO> users = userMapper.selectUsersWithRoles();
// 必须修复:XSS 漏洞
// 不推荐
div.innerHTML = userInput;
// 推荐
div.textContent = userInput;
// 或使用 DOMPurify 等库进行清理
div.innerHTML = DOMPurify.sanitize(userInput);
实际项目示例:
// 建议修复:命名不规范
// 不推荐
public class Usr { }
public void getUsrInfo() { }
// 推荐
public class User { }
public void getUserInfo() { }
// 建议修复:重复代码
// 不推荐
public void createUser(UserDTO user) {
if (user.getUsername() == null || user.getUsername().isEmpty()) {
throw new BusinessException("用户名不能为空");
}
if (user.getEmail() == null || user.getEmail().isEmpty()) {
throw new BusinessException("邮箱不能为空");
}
}
public void updateUser(UserDTO user) {
if (user.getUsername() == null || user.getUsername().isEmpty()) {
throw new BusinessException("用户名不能为空");
}
if (user.getEmail() == null || user.getEmail().isEmpty()) {
throw new BusinessException("邮箱不能为空");
}
}
// 推荐
private void validateUser(UserDTO user) {
if (StringUtils.isBlank(user.getUsername())) {
throw new BusinessException("用户名不能为空");
}
if (StringUtils.isBlank(user.getEmail())) {
throw new BusinessException("邮箱不能为空");
}
}
public void createUser(UserDTO user) {
validateUser(user);
}
public void updateUser(UserDTO user) {
validateUser(user);
}
<type>(<scope>): <subject>
<body>
<footer>
feat(user): 新增用户头像上传功能
1. 添加头像上传接口
2. 实现头像裁剪功能
3. 更新用户信息页面
Closes #123
良好的代码规范和最佳实践是保证项目质量的重要基础。通过遵循本文档的规范和建议,我们可以:
希望所有开发者都能严格遵循这些规范和最佳实践,共同维护一个高质量的代码库。