开发文档
项目代码层面的详细使用说明,包括架构设计、公共方法、权限控制、API 规范等。
1. 架构概览
Maven 多模块分层架构
| 模块 | 职责 | 依赖 |
|---|---|---|
haoyu-admin-start | 启动入口、配置文件(application.yml) | 所有业务模块 |
haoyu-common | 公共工具类、注解、常量、异常处理 | 无 |
haoyu-framework | 框架配置(跨域、MyBatis、Redis、Sa-Token、拦截器) | haoyu-common |
haoyu-system | 系统管理(用户、角色、菜单、部门、岗位、日志、文件) | haoyu-framework |
haoyu-workflow | 工作流引擎(流程定义、审批任务、表单设计) | haoyu-framework |
haoyu-report | 报表模块(报表设计器、大屏设计器、数据源管理) | haoyu-framework |
haoyu-generator | 代码生成(表结构设计、模板引擎、代码下载) | haoyu-framework |
2. 统一响应格式 AjaxResult
所有 API 接口统一使用 AjaxResult 封装返回结果
package com.haoyu888.common.core;
public class AjaxResult<T> implements Serializable {
private int code; // 状态码,200=成功,500=失败
private String msg; // 提示消息
private T data; // 响应数据
// 成功(无数据)
public static AjaxResult<Void> success();
// 成功(带数据)
public static <T> AjaxResult<T> success(T data);
// 成功(自定义消息 + 数据)
public static <T> AjaxResult<T> success(String msg, T data);
// 失败
public static <T> AjaxResult<T> error(String msg);
public static <T> AjaxResult<T> error(int code, String msg);
}
使用示例:
@GetMapping("/list")
public AjaxResult<List<SysUser>> list(SysUser user) {
List<SysUser> list = userService.selectUserList(user);
return AjaxResult.success(list);
}
@PostMapping
public AjaxResult<Void> add(@Valid @RequestBody SysUser user) {
userService.insertUser(user);
return AjaxResult.success();
}
3. 分页查询 PageQuery
所有分页查询继承 PageQuery 基类
package com.haoyu888.common.core;
@Data
public class PageQuery {
private Integer pageNum = 1; // 当前页码
private Integer pageSize = 10; // 每页条数
private String orderByColumn; // 排序列
private String isAsc; // 排序方向 asc/desc
// 获取偏移量
public int getOffset() {
return (pageNum - 1) * pageSize;
}
}
BaseEntity 继承自 PageQuery,自动携带分页参数:
@Data
@EqualsAndHashCode(callSuper = true)
public class BaseEntity extends PageQuery implements Serializable {
private Long createBy; // 创建者
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime; // 创建时间
private Long updateBy; // 更新者
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime; // 更新时间
private String remark; // 备注
@JsonIgnore
private Map<String, Object> params; // 请求参数
}
4. 权限认证体系
基于 Sa-Token 的 RBAC 权限模型
4.1 认证流程
// 登录逻辑
@PostMapping("/login")
public AjaxResult login(String username, String password, String code) {
// 1. 验证码校验
// 2. 用户查询 + 密码校验(BCrypt)
// 3. StpUtil.login(userId) 创建会话
// 4. 将 LoginUser 存入 Session
// 5. 返回 Token
}
// Token 传递方式:请求头 Authorization
// 配置:sa-token.token-name: Authorization
4.2 权限注解使用
// 角色权限:需要 admin 角色
@SaCheckRole("admin")
@DeleteMapping("/{userIds}")
public AjaxResult remove(@PathVariable Long[] userIds) {
return toAjax(userService.deleteUserByIds(userIds));
}
// 菜单权限:需要 system:user:list 权限
@SaCheckPermission("system:user:list")
@GetMapping("/list")
public AjaxResult list(SysUser user) { ... }
// 多个权限(AND 关系)
@SaCheckPermission(value = {"system:user:add", "system:user:edit"})
// 登录校验(排除路径在 SaTokenConfig 中配置)
// 排除路径:/login, /captchaImage, /profile/** 等
4.3 SecurityUtils 工具类
package com.haoyu888.common.utils;
public class SecurityUtils {
// 获取当前登录用户对象
public static LoginUser getLoginUser();
// 获取当前登录用户ID
public static Long getUserId();
// 获取当前登录用户名
public static String getUsername();
// 判断是否超级管理员
public static boolean isSuperAdmin(Long userId);
// 生成随机密码(8位)
public static String generateRandomPassword();
}
5. 操作日志注解 @Log
在 Controller 方法上添加 @Log 注解自动记录操作日志
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String title() default ""; // 操作标题
BusinessType businessType() default OTHER; // 业务类型
boolean isSaveRequestData() default true; // 是否保存请求参数
boolean isSaveResponseData() default true; // 是否保存响应数据
}
// BusinessType 枚举值:
// OTHER, INSERT, UPDATE, DELETE, EXPORT, IMPORT, GRANT, FORCE
使用示例:
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@SaCheckPermission("system:user:add")
@PostMapping
public AjaxResult add(@Valid @RequestBody SysUser user) {
return toAjax(userService.insertUser(user));
}
6. 数据权限 @DataScope
通过 AOP 切面自动拼接 SQL 过滤条件
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {
String deptAlias() default ""; // 部门表别名
String userAlias() default ""; // 用户表别名
DataScopeType type() default DataScopeType.CUSTOM;
}
// DataScopeType 数据范围类型:
// ALL - 全部数据
// CUSTOM - 自定义数据(角色关联的部门)
// DEPT - 本部门数据
// DEPT_AND_CHILD - 本部门及以下数据
// SELF - 仅本人数据
使用示例:
@DataScope(deptAlias = "d", userAlias = "u")
public List<SysUser> selectUserList(SysUser user) {
// AOP 切面会自动在 SQL 后追加权限过滤条件
// 例如:AND (d.dept_id = 100 OR d.dept_id IN (SELECT ...))
return userMapper.selectUserList(user);
}
7. 公共工具类
IpUtils - IP 地址工具
package com.haoyu888.common.utils;
public class IpUtils {
// 获取客户端 IP(支持 X-Forwarded-For / X-Real-IP 代理头)
public static String getIp();
// 获取 IP 对应的地理位置
public static String getLocation(String ip);
}
UserAgentUtils - 浏览器/设备识别
package com.haoyu888.common.utils;
public class UserAgentUtils {
// 解析 User-Agent 字符串,返回浏览器名称和设备信息
public static String getBrowser(String userAgent);
public static String getOs(String userAgent);
}
PasswordVerifyService - 密码验证服务
// 公共密码二次验证服务,供日志删除等敏感操作使用
@Service
public class PasswordVerifyService {
// 校验当前登录用户输入的密码是否匹配
public void verifyPassword(String rawPassword) {
Long userId = SecurityUtils.getUserId();
SysUser user = userMapper.selectUserById(userId);
if (!BCrypt.checkpw(rawPassword, user.getPassword())) {
throw new BusinessException("密码验证失败");
}
}
}
8. 全局异常处理
通过 GlobalExceptionHandler 统一捕获并返回错误信息
@RestControllerAdvice
public class GlobalExceptionHandler {
// 业务异常
@ExceptionHandler(BusinessException.class)
public AjaxResult handleBusiness(BusinessException e) {
return AjaxResult.error(e.getCode(), e.getMessage());
}
// Sa-Token 未登录异常
@ExceptionHandler(NotLoginException.class)
public AjaxResult handleNotLogin(NotLoginException e) {
return AjaxResult.error(401, "未登录或登录已过期");
}
// 参数校验异常(@Valid 触发)
@ExceptionHandler(MethodArgumentNotValidException.class)
public AjaxResult handleValid(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldError().getDefaultMessage();
return AjaxResult.error(msg);
}
// 权限不足
@ExceptionHandler(NotPermissionException.class)
public AjaxResult handlePermission(NotPermissionException e) {
return AjaxResult.error(403, "没有权限");
}
}
业务代码中抛出异常:
// 抛出业务异常
throw new BusinessException("用户名已存在");
throw new BusinessException("密码长度不能少于5位");
9. 文件上传
文件上传接口说明与限制
| 配置项 | 值 | 说明 |
|---|---|---|
| 接口地址 | POST /common/upload | 需要登录认证 |
| 文件大小限制 | 10MB | Spring multipart + 控制器双重校验 |
| 文件类型白名单 | 图片/文档/压缩包等 | jpg, png, gif, pdf, doc, xlsx 等 |
| 存储路径 | uploadPath 配置项 | 通过 application.yml 配置 |
| 访问方式 | /profile/files/** | 静态资源映射 |
// 前端上传示例(el-upload)
<el-upload
action="/api/common/upload"
:headers="uploadHeaders"
:before-upload="beforeUpload"
accept=".jpg,.png,.pdf,.doc,.xlsx">
<el-button type="primary">上传文件</el-button>
</el-upload>
// uploadHeaders 自动携带 Authorization Token
const uploadHeaders = computed(() => ({
Authorization: 'Bearer ' + getToken()
}))
10. API 文档(Knife4j)
基于 Knife4j + SpringDoc 自动生成 API 文档
// 所有 Controller 接口均添加 @Operation 注解
@Tag(name = "用户管理")
@RestController
@RequestMapping("/system/user")
public class SysUserController {
@Operation(summary = "获取用户列表")
@SaCheckPermission("system:user:list")
@GetMapping("/list")
public AjaxResult list(SysUser user) { ... }
@Operation(summary = "新增用户")
@SaCheckPermission("system:user:add")
@PostMapping
public AjaxResult add(@Valid @RequestBody SysUser user) { ... }
}
// 访问地址:http://localhost:8080/doc.html
// 生产环境可通过配置关闭
11. 前端开发规范
目录结构
haoyu-ui/src/
├── api/ # 接口请求(按模块拆分)
│ ├── auth.js # 登录认证
│ ├── system.js # 系统管理
│ ├── workflow.js # 工作流
│ ├── report.js # 报表模块
│ └── generator.js # 代码生成
├── components/ # 公共组件
│ ├── CaptchaImage/ # 验证码
│ ├── DeptSelect/ # 部门选择器
│ ├── FileUpload/ # 文件上传
│ ├── ImageUpload/ # 图片上传
│ └── UserSelect/ # 用户选择器
├── layout/ # 布局组件
├── router/ # 路由配置
├── store/ # Pinia 状态管理
├── utils/ # 工具函数
│ ├── request.js # axios 封装
│ ├── auth.js # Token 管理
│ └── ...
└── views/ # 页面视图
请求封装 request.js
import axios from 'axios'
import { getToken } from '@/utils/auth'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // /api
timeout: 30000
})
// 请求拦截器:自动携带 Token
service.interceptors.request.use(config => {
const token = getToken()
if (token) {
config.headers['Authorization'] = 'Bearer ' + token
}
return config
})
// 响应拦截器:统一错误处理
service.interceptors.response.use(response => {
const res = response.data
if (res.code !== 200) {
// 401: Token 过期
if (res.code === 401) { logout() }
ElMessage.error(res.msg || '请求失败')
return Promise.reject(new Error(res.msg))
}
return res
})
代理配置 vite.config.js
export default defineConfig({
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
12. 安全机制
| 安全项 | 实现方式 | 说明 |
|---|---|---|
| 认证 | Sa-Token | Token 认证,支持同端互斥登录 |
| 密码加密 | BCrypt | 密码存储使用 BCrypt 哈希 |
| SQL 注入防护 | MyBatis #{} + 白名单校验 | 参数化查询 + 表名格式校验 |
| XSS 防护 | escapeHtml() 转义 | 前端日志展示使用 HTML 转义 |
| 输入校验 | @Valid + @NotBlank/@Size | 实体类字段级校验 |
| 文件上传 | 类型白名单 + 大小限制 | 仅允许指定文件类型,最大 10MB |
| 敏感操作 | 密码二次验证 | 日志删除等操作需输入密码确认 |
| 数据权限 | AOP 切面 + SQL 过滤 | 基于角色的数据范围控制 |
| 请求日志 | 拦截器 + 密码脱敏 | 自动记录请求日志,密码字段脱敏 |
| 报表 SQL | SqlSafetyChecker | 仅允许 SELECT 语句,禁止写操作 |