开发文档

项目代码层面的详细使用说明,包括架构设计、公共方法、权限控制、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需要登录认证
文件大小限制10MBSpring 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-TokenToken 认证,支持同端互斥登录
密码加密BCrypt密码存储使用 BCrypt 哈希
SQL 注入防护MyBatis #{} + 白名单校验参数化查询 + 表名格式校验
XSS 防护escapeHtml() 转义前端日志展示使用 HTML 转义
输入校验@Valid + @NotBlank/@Size实体类字段级校验
文件上传类型白名单 + 大小限制仅允许指定文件类型,最大 10MB
敏感操作密码二次验证日志删除等操作需输入密码确认
数据权限AOP 切面 + SQL 过滤基于角色的数据范围控制
请求日志拦截器 + 密码脱敏自动记录请求日志,密码字段脱敏
报表 SQLSqlSafetyChecker仅允许 SELECT 语句,禁止写操作