Compare commits

..

12 Commits

@ -0,0 +1,18 @@
# ECMS 文档合集
> 本文件由导出脚本自动生成(也可手动维护)。
<!-- 目录由导出脚本使用 pandoc --toc 生成 -->
## 包含内容
- SRS软件需求规格说明
- IRS接口需求规格说明
- 体系结构设计报告
- 详细设计报告
- 设计图示(类图/时序图)
- 单元测试计划
- 集成测试用例集
- 部署说明
- 研发流程规范

@ -0,0 +1,72 @@
软件体系结构设计报告(总体设计)
1. 概述
本报告描述ECMS系统的总体架构与关键设计决策严格对应当前代码实现。系统采用前后端分离架构后端Spring Boot提供REST接口前端Vue提供界面与路由导航。
2. 架构概览
- 分层:
- 接口层Controller`com.prj.controller.*`提供REST接口、分页与统一结果包装
- 业务层Service`com.prj.service.*` 与 `impl`,封装业务逻辑与事务;
- 数据访问层Mapper/Repo`com.prj.mapper.*`MyBatis与 `com.prj.repo.*`JPA/Redis
- 领域模型:`com.prj.domain.*`
- 框架与配置:`com.prj.framework.*`、`com.prj.common.*`
- 前端:
- 路由:`src/router/index.js`
- API`src/api/*.js` 通过 `@/utils/request` 调用后端;
- 状态:`src/store`
3. 模块划分与依赖
- 用户与安全:`LoginController`、`CaptchaController`、`framework.security` 与 `framework.web.service.LoginService`
- 部门管理:`DeptController` ↔ `IDeptService` ↔ `DeptMapper.xml` ↔ `Dept`
- 员工管理:`EmployeeController` ↔ `IEmployeeService` ↔ `EmployeeMapper.xml` ↔ `Employee`
- 员工评价:`EmployeeKpiController` ↔ `IEmployeeKpiService` ↔ `EmployeeKpiMapper.xml` ↔ `EmployeeKpi`
- 招人名额:`HireNumController` ↔ `IHireNumService` ↔ `HirenumRepo`/`HireNumMapper.xml` ↔ `HireNum`实体带JPA注解
- 薪资标准:`SalaryLevelController` ↔ `ISalaryLevelService` ↔ `SalaryLevelMapper.xml` ↔ `SalaryLevel`
- 缓存:`RedisCache` 用于验证码;
4. 关键设计
4.1 统一返回与分页
- `AjaxResult` 统一返回格式;
- `BaseController` 提供 `startPage()` 与 `getDataByPage()`,确保各列表接口一致的分页行为。
4.2 事务
- `EmployeeController#edit` 使用 `@Transactional` 保证更新一致性;其他事务由服务层按需控制。
4.3 数据持久化
- 以MyBatis XML为主见 `resources/mapper/*.xml``HireNum` 同时具备JPA实体注解并通过 `HirenumRepo` 使用;
- 领域模型简洁,字段即数据库列的直接映射;
4.4 安全与会话
- 登录流程验证码生成与Redis校验 → `LoginService.login` 签发 `token`
- 前端拦截器自动在请求头附加 `Authorization`401时触发登出与跳转。
4.5 前端结构
- 路由按照模块分组(部门/员工/评价/招聘名额/薪资标准)与首页、登录;
- API封装统一超时、错误码处理与消息提示
5. 接口与数据流
- 前端页面触发 API 请求 → `@/utils/request` 注入 `Authorization` → 后端Controller → Service → Mapper/Repo → 数据库/缓存 → 结果包装返回。
6. 日志与监控
- 使用 `logback.xml` 配置日志生产环境可扩展接入APM或集中式日志当前代码未包含
7. 配置与环境
- 后端配置:`src/main/resources/application.yml`
- 数据源/Redis等依赖通过Spring配置加载当前文件中可见Redis用于验证码
8. 集成测试计划(概要)
- 鉴权链路联调:`/captchaImage` → `/login` → 携带 `Authorization` 调用任一CRUD接口验证401/200分支
- 各模块CRUD链路前端各页面操作对应API验证分页、详情、创建、更新、删除端到端行为
- 事务验证:并发/异常场景下 `PUT /employee` 数据一致性;
9. 部署与分支规范
- 主分支 `main`,目录结构 `doc/` 与 `src/`
- 开发者以个人分支开发,提交合并请求以便指导老师审阅参与度;
- Windows 开发环境,前端、后端分别启动;
10. 演进与扩展性
- 可在Service层引入DTO与校验
- 可在安全层引入基于角色的访问控制;
- 可为各模块添加审计日志与操作记录。

@ -0,0 +1,71 @@
package com.prj.controller;
import com.google.code.kaptcha.Producer;
import com.prj.common.constant.Constants;
import com.prj.common.core.domain.AjaxResult;
import com.prj.common.core.redis.RedisCache;
import com.prj.common.utils.sign.Base64;
import com.prj.common.utils.uuid.IdUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@RestController
public class CaptchaController
{
@Resource(name = "captchaProducerMath")
private Producer captchaProducerMath
;
@Autowired
private RedisCache redisCache;
/**
*
*/
@GetMapping("/captchaImage")
public AjaxResult getCode(HttpServletResponse response) throws IOException
{
AjaxResult ajax = AjaxResult.success();
// 保存验证码信息
String uuid = IdUtils.simpleUUID();
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String capStr = null, code = null;
BufferedImage image = null;
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
image = captchaProducerMath.createImage(capStr);
redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try
{
ImageIO.write(image, "jpg", os);
}
catch (IOException e)
{
return AjaxResult.error(e.getMessage());
}
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
}
}

@ -0,0 +1,51 @@
部署说明Windows 环境)
1. 环境准备
- 操作系统Windows 10/11
- JDK8+(建议 11
- Node.js16+(与项目 `package.json` 兼容)
- Redis用于验证码缓存
2. 后端Spring Boot
2.1 进入目录
`backend/backend/prj-backendSpring Boot版`
2.2 配置
- 编辑 `src/main/resources/application.yml`
- 数据库连接url、username、password
- Redis 连接
2.3 编译与运行
- 使用IDEIntelliJ IDEA/Eclipse导入Maven项目并运行 `SpringBootApplication`
- 或命令行PowerShell
- `mvn -v`确认Maven可用
- `mvn clean package -DskipTests`
- 运行:`java -jar target/*.jar`
3. 前端Vue
3.1 进入目录
`frontend`
3.2 安装依赖(首次)
`npm install`
3.3 配置
- 在根目录或 `.env.*` 中设置 `VUE_APP_BASE_API` 指向后端地址(如 `http://localhost:8080`)。
3.4 启动
`npm run serve`
4. 访问
- 前端开发地址:`http://localhost:8080`(或控制台实际端口)
- 登录页:`/login`;首页:`/index`
5. 常见问题
- 401确认登录成功并在请求头携带 `Authorization: Bearer <token>`
- 验证码过期:重新获取 `/captchaImage`
- 超时:前端默认 2000ms可在 `src/utils/request.js` 调整。
6. 目录规范
- 主分支:`main`;结构包含 `doc/` 与 `src/`
- 前后端独立启动;

@ -0,0 +1,70 @@
package com.prj.domain;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class Dept
{
private static final long serialVersionUID = 1L;
/** 部门编号 */
private Long id;
/** 部门名 */
private String name;
/** 部门经理 */
private String manager;
/** 汇报对象 */
private String reportto;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void setManager(String manager)
{
this.manager = manager;
}
public String getManager()
{
return manager;
}
public void setReportto(String reportto)
{
this.reportto = reportto;
}
public String getReportto()
{
return reportto;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("name", getName())
.append("manager", getManager())
.append("reportto", getReportto())
.toString();
}
}

@ -0,0 +1,75 @@
package com.prj.controller;
import java.util.List;
import com.prj.common.core.controller.BaseController;
import com.prj.common.core.domain.AjaxResult;
import com.prj.common.core.page.TableDataInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.prj.domain.Dept;
import com.prj.service.IDeptService;
@RestController
@RequestMapping("/dept_info")
public class DeptController extends BaseController
{
@Autowired
private IDeptService deptService;
/**
*
*/
@GetMapping("/list")
public TableDataInfo list(Dept dept)
{
startPage();
List<Dept> list = deptService.selectDeptList(dept);
return getDataByPage(list);
}
/**
*
*/
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return AjaxResult.success(deptService.selectDeptById(id));
}
/**
*
*/
@PostMapping
public AjaxResult add(@RequestBody Dept dept)
{
return toAjax(deptService.insertDept(dept));
}
/**
*
*/
@PutMapping
public AjaxResult edit(@RequestBody Dept dept)
{
return toAjax(deptService.updateDept(dept));
}
/**
*
*/
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(deptService.deleteDeptByIds(ids));
}
}

@ -0,0 +1,75 @@
设计图示(类图与时序图)
1. 类关系图(简化)
```mermaid
classDiagram
class DeptController
class EmployeeController
class EmployeeKpiController
class HireNumController
class SalaryLevelController
class LoginController
class CaptchaController
class IDeptService
class IEmployeeService
class IEmployeeKpiService
class IHireNumService
class ISalaryLevelService
class Dept
class Employee
class EmployeeKpi
class HireNum
class SalaryLevel
DeptController --> IDeptService
EmployeeController --> IEmployeeService
EmployeeKpiController --> IEmployeeKpiService
HireNumController --> IHireNumService
SalaryLevelController --> ISalaryLevelService
IDeptService --> Dept
IEmployeeService --> Employee
IEmployeeKpiService --> EmployeeKpi
IHireNumService --> HireNum
ISalaryLevelService --> SalaryLevel
```
2. 登录时序图
```mermaid
sequenceDiagram
participant U as User
participant FE as Frontend(Vue)
participant BE as Backend(Spring Boot)
participant REDIS as Redis
U->>FE: 打开登录页
FE->>BE: GET /captchaImage
BE->>REDIS: 保存(uuid -> code)
BE-->>FE: uuid, img(Base64)
U->>FE: 输入 username/password/code
FE->>BE: POST /login {username,password,code,uuid}
BE->>REDIS: 校验 code
BE-->>FE: token
FE->>FE: 保存 Admin-Token(Cookie)
```
3. 分页查询时序图
```mermaid
sequenceDiagram
participant FE as Frontend
participant CTRL as Controller
participant SVC as Service
participant DAO as Mapper/Repo
FE->>CTRL: GET /<module>/list?query
CTRL->>CTRL: startPage()
CTRL->>SVC: select*List(query)
SVC->>DAO: 查询
DAO-->>SVC: 列表数据
SVC-->>CTRL: 列表数据
CTRL-->>FE: TableDataInfo
```

@ -0,0 +1,107 @@
软件详细设计报告(详细设计)
1. 总体说明
本报告基于当前代码实现,细化至类/接口、方法职责、数据流与异常处理,覆盖后端各模块与前端关键交互点。
2. 后端详细设计
2.1 统一基础设施
- `com.prj.common.core.controller.BaseController`
- `startPage()`:开启分页(集成 MyBatis PageHelper
- `getDataByPage(List)`:封装为 `TableDataInfo`
- `toAjax(int rows)`:封装影响行数到 `AjaxResult`
- `AjaxResult`:统一返回结构 `{ code, msg, data }`
2.2 鉴权与验证码
- `CaptchaController`
- 依赖:`captchaProducerMath`、`RedisCache`
- 主要流程:生成运算验证码 → Redis存储(键:`Constants.CAPTCHA_CODE_KEY + uuid`) → Base64图片返回
- 异常:`ImageIO.write` 抛出 `IOException` 时,返回 `AjaxResult.error(msg)`
- `LoginController`
- 依赖:`LoginService`
- 主要流程:校验验证码 → 账号认证 → 生成 `token` → 返回 `Constants.TOKEN` 字段
2.3 部门信息管理
- `DeptController`
- `GET /dept_info/list``startPage()` → `IDeptService.selectDeptList(Dept)` → `getDataByPage(list)`
- `GET /dept_info/{id}``IDeptService.selectDeptById(id)`
- `POST /dept_info``IDeptService.insertDept(Dept)` → `toAjax(rows)`
- `PUT /dept_info``IDeptService.updateDept(Dept)` → `toAjax(rows)`
- `DELETE /dept_info/{ids}``IDeptService.deleteDeptByIds(Long[])` → `toAjax(rows)`
- Service 与持久化
- `IDeptService`/impl参数校验与 CRUD 组合;
- `DeptMapper.xml`:对应 SQL与实体字段一致
2.4 员工信息管理
- `EmployeeController`
- 同 Dept 接口集合;
- `PUT /employee``@Transactional(timeout=20, readOnly=false, isolation=DEFAULT, propagation=REQUIRED)`
- 设计目的:保证更新过程中的一致性,可在方法内扩展多表写操作;
- Service 与持久化
- `IEmployeeService`/impl` + `EmployeeMapper.xml`
2.5 员工评价管理
- `EmployeeKpiController` + `IEmployeeKpiService` + `EmployeeKpiMapper.xml`
- 五个CRUD端点同上模式
2.6 招人名额管理
- `HireNumController` + `IHireNumService` + `HirenumRepo`/`HireNumMapper.xml`
- 实体 `HireNum` 使用 JPA 注解(`@Entity @Table(name="hire_num")`),字段 `id/dept/num/endtime`
2.7 薪资标准管理
- `SalaryLevelController` + `ISalaryLevelService` + `SalaryLevelMapper.xml`
2.8 领域模型
- `Dept{id, name, manager, reportto}`
- `Employee{id, dept, name, position, salary}`
- `EmployeeKpi{id, kpi, bonus, manager}`
- `HireNum{id, dept, num, endtime}`
- `SalaryLevel{id, jobType, salary, salaryrange}`
2.9 异常与错误处理
- 控制器返回 `AjaxResult.error(msg)` 或 `code!=200`
- 登录401由前端捕获并触发重新登录
3. 前端详细设计
3.1 路由
- `src/router/index.js`
- `/index` 首页;
- 业务模块:`/dept_info`、`/salary_level`、`/hirenum`、`/employee`、`/employee_kpi`
- `/login` 登录页;
3.2 请求封装
- `src/utils/request.js`
- Request 拦截器:若存在 `Admin-Token`,注入 `Authorization: Bearer <token>`
- Response 拦截器:基于 `code` 统一处理 401/500/非200 异常;
- `timeout=2000ms``baseURL=VUE_APP_BASE_API`
- `src/utils/auth.js`:基于 Cookie 管理 `Admin-Token`
3.3 API 模块
- `src/api/*.js` 与后端控制器一一对应URL 完全匹配),方法:`list/get/add/update/del`
4. 典型时序(文本)
4.1 登录
用户 → `/captchaImage` → 返回 `uuid,img`;用户提交 `/login{username,password,code,uuid}` → 返回 `token`;前端保存 Cookie 并携带到后续请求。
4.2 列表分页
页面 → 调用 `list` APIquery→实体字段映射→ Controller `startPage()` → Service/Mapper 查询 → `TableDataInfo` 返回。
4.3 更新(员工示例)
页面 → `PUT /employee` → Controller事务→ Service → Mapper/JPA → 受影响行数 → `AjaxResult`。
5. 数据校验与约束
- 控制器层假定实体字段按类型传入;
- 业务层可扩展非空、范围、唯一性校验(当前代码未显式实现高级校验规则)。
6. 配置要点
- `application.yml`:数据源/端口/Redis等
- `logback.xml`:日志级别与输出;
7. 安全与权限
- 以 Token 作为会话凭证;未实现细粒度 RBAC 的控制器级别权限;
8. 扩展点
- 在Service引入DTO与校验框架
- 统一异常处理与错误码枚举;
- 审计日志与操作记录;

@ -0,0 +1,35 @@
研发流程规范
1. 分支策略
- 主分支:`main`(包含 `doc/` 与 `src/`
- 个人分支:每人各建一个分支,例如 `feature/<name>` 或 `user/<name>`
- 合并策略:先将更改提交到个人分支,发起 Merge Request 到 `main`,由指导老师/管理员评审后合并;
2. 提交流程
- 提交前:
- 更新/新增文档SRS/IRS/Architecture/DetailedDesign/测试计划/用例/设计图示)
- 自测:前后端基础运行、主要用例走通;
- 提交信息规范:
- `feat:` 新功能;`fix:` 修复;`docs:` 文档;`refactor:` 重构;`test:` 测试;
- 简明描述变更范围与影响模块;
3. 评审要点
- 代码可读性与一致性(命名、格式、错误处理);
- 与文档一致API/路由/数据库字段变更需同步文档);
- 单元/集成测试是否覆盖关键路径;
4. 版本与标记
- 以里程碑或迭代结束在 `main` 打标签(如 `v0.1.0`
5. 变更同步
- 当 API、路由、数据库字段有变更
- IRS/SRS 第3~5章更新
- DetailedDesign 对应小节更新
- 测试计划与用例同步调整
- 设计图示(类图/时序图)如有结构变化也需更新
6. Windows 环境注意
- 命令行不要使用 `&&` 连续执行;
- 确认 Redis/数据库服务已启动;

@ -0,0 +1,85 @@
package com.prj.domain;
import java.math.BigDecimal;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class Employee
{
private static final long serialVersionUID = 1L;
/** 编号 */
private Long id;
/** 部门 */
private String dept;
/** 姓名 */
private String name;
/** 职位 */
private String position;
/** 薪资 */
private BigDecimal salary;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setDept(String dept)
{
this.dept = dept;
}
public String getDept()
{
return dept;
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void setPosition(String position)
{
this.position = position;
}
public String getPosition()
{
return position;
}
public void setSalary(BigDecimal salary)
{
this.salary = salary;
}
public BigDecimal getSalary()
{
return salary;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("dept", getDept())
.append("name", getName())
.append("position", getPosition())
.append("salary", getSalary())
.toString();
}
}

@ -0,0 +1,80 @@
package com.prj.controller;
import java.util.List;
import com.prj.common.core.controller.BaseController;
import com.prj.common.core.domain.AjaxResult;
import com.prj.common.core.page.TableDataInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.prj.domain.Employee;
import com.prj.service.IEmployeeService;
@RestController
@RequestMapping("/employee")
public class EmployeeController extends BaseController
{
@Autowired
private IEmployeeService employeeService;
/**
*
*/
@GetMapping("/list")
public TableDataInfo list(Employee employee)
{
startPage();
List<Employee> list = employeeService.selectEmployeeList(employee);
return getDataByPage(list);
}
/**
*
*/
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return AjaxResult.success(employeeService.selectEmployeeById(id));
}
/**
*
*/
@PostMapping
public AjaxResult add(@RequestBody Employee employee)
{
return toAjax(employeeService.insertEmployee(employee));
}
/**
*
*/
@PutMapping
@Transactional(timeout = 20,readOnly = false,isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
public AjaxResult edit(@RequestBody Employee employee)
{
//其它针对数据库的操作
return toAjax(employeeService.updateEmployee(employee));
}
/**
*
*/
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(employeeService.deleteEmployeeByIds(ids));
}
}

@ -0,0 +1,71 @@
package com.prj.domain;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class EmployeeKpi
{
private static final long serialVersionUID = 1L;
/** 员工编号 */
private Long id;
/** 考评结果 */
private String kpi;
/** 奖金 */
private String bonus;
/** 考评人 */
private String manager;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setKpi(String kpi)
{
this.kpi = kpi;
}
public String getKpi()
{
return kpi;
}
public void setBonus(String bonus)
{
this.bonus = bonus;
}
public String getBonus()
{
return bonus;
}
public void setManager(String manager)
{
this.manager = manager;
}
public String getManager()
{
return manager;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("kpi", getKpi())
.append("bonus", getBonus())
.append("manager", getManager())
.toString();
}
}

@ -0,0 +1,76 @@
package com.prj.controller;
import java.util.List;
import com.prj.common.core.controller.BaseController;
import com.prj.common.core.domain.AjaxResult;
import com.prj.common.core.page.TableDataInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.prj.domain.EmployeeKpi;
import com.prj.service.IEmployeeKpiService;
@RestController
@RequestMapping("/employee_kpi")
public class EmployeeKpiController extends BaseController
{
@Autowired
private IEmployeeKpiService employeeKpiService;
/**
*
*/
@GetMapping("/list")
public TableDataInfo list(EmployeeKpi employeeKpi)
{
startPage();
List<EmployeeKpi> list = employeeKpiService.selectEmployeeKpiList(employeeKpi);
return getDataByPage(list);
}
/**
*
*/
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return AjaxResult.success(employeeKpiService.selectEmployeeKpiById(id));
}
/**
*
*/
@PostMapping
public AjaxResult add(@RequestBody EmployeeKpi employeeKpi)
{
return toAjax(employeeKpiService.insertEmployeeKpi(employeeKpi));
}
/**
*
*/
@PutMapping
public AjaxResult edit(@RequestBody EmployeeKpi employeeKpi)
{
return toAjax(employeeKpiService.updateEmployeeKpi(employeeKpi));
}
/**
*
*/
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(employeeKpiService.deleteEmployeeKpiByIds(ids));
}
}

@ -0,0 +1,81 @@
package com.prj.domain;
import java.math.BigDecimal;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import javax.persistence.*;
@Entity
@Table(name="hire_num")
public class HireNum
{
private static final long serialVersionUID = 1L;
/** 编号 */
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
/** 部门 */
@Column(name = "dept")
private String dept;
/** 招人名额 */
@Column(name = "num")
private BigDecimal num;
/** 截止时间 */
@Column(name = "endtime")
private String endtime;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setDept(String dept)
{
this.dept = dept;
}
public String getDept()
{
return dept;
}
public void setNum(BigDecimal num)
{
this.num = num;
}
public BigDecimal getNum()
{
return num;
}
public void setEndtime(String endtime)
{
this.endtime = endtime;
}
public String getEndtime()
{
return endtime;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("dept", getDept())
.append("num", getNum())
.append("endtime", getEndtime())
.toString();
}
}

@ -0,0 +1,75 @@
package com.prj.controller;
import java.util.List;
import com.prj.common.core.controller.BaseController;
import com.prj.common.core.domain.AjaxResult;
import com.prj.common.core.page.TableDataInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.prj.domain.HireNum;
import com.prj.service.IHireNumService;
@RestController
@RequestMapping("/hirenum")
public class HireNumController extends BaseController
{
@Autowired
private IHireNumService hireNumService;
/**
*
*/
@GetMapping("/list")
public TableDataInfo list(HireNum hireNum)
{
startPage();
List<HireNum> list = hireNumService.selectHireNumList(hireNum);
return getDataByPage(list);
}
/**
*
*/
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return AjaxResult.success(hireNumService.selectHireNumById(id));
}
/**
*
*/
@PostMapping
public AjaxResult add(@RequestBody HireNum hireNum)
{
return toAjax(hireNumService.insertHireNum(hireNum));
}
/**
*
*/
@PutMapping
public AjaxResult edit(@RequestBody HireNum hireNum)
{
return toAjax(hireNumService.updateHireNum(hireNum));
}
/**
*
*/
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(hireNumService.deleteHireNumByIds(ids));
}
}

@ -0,0 +1,19 @@
package com.prj.service;
import java.util.List;
import com.prj.domain.Dept;
public interface IDeptService
{
public Dept selectDeptById(Long id);
public List<Dept> selectDeptList(Dept dept);
public int insertDept(Dept dept);
public int updateDept(Dept dept);
public int deleteDeptByIds(Long[] ids);
}

@ -0,0 +1,19 @@
package com.prj.service;
import java.util.List;
import com.prj.domain.Employee;
public interface IEmployeeService
{
public Employee selectEmployeeById(Long id);
public List<Employee> selectEmployeeList(Employee employee);
public int insertEmployee(Employee employee);
public int updateEmployee(Employee employee);
public int deleteEmployeeByIds(Long[] ids);
}

107
IRS

@ -0,0 +1,107 @@
接口需求规格说明IRS
1. 引言
本文件列出系统对外提供的HTTP/JSON接口规范严格依据当前代码实现与前端调用编写。
2. 接口通用规范
2.1 协议与认证
- 协议HTTP/HTTPSJSON 编码;
- 认证:除登录与验证码外,默认需在请求头携带 `Authorization: Bearer <token>`
- Token 获取:通过 `POST /login` 成功登录后,在响应体中以 `Constants.TOKEN` 键返回;前端将其以 `Admin-Token` Cookie 持久化,并在请求拦截器自动附加到请求头。
2.2 响应包装
- 成功:`{ code: 200, msg: 'success', data: any }`
- 失败:`{ code: 非200, msg: string }`
- 分页:`TableDataInfo`(包含列表与分页元数据,前端直接使用返回体)
2.3 错误码约定(前端处理)
- 401登录过期或未认证需重新登录
- 500服务端错误
- 其他非200按 `src/utils/errorCode.js` 进行展示。
2.4 超时与BaseURL
- BaseURL`process.env.VUE_APP_BASE_API`
- 超时2000ms
3. 接口清单
3.1 验证码
GET /captchaImage
- 功能:生成验证码图片与 `uuid`并在Redis保存验证码文本`Constants.CAPTCHA_CODE_KEY + uuid`)。
- 请求头:无特殊要求
- 请求参数:无
- 响应体:`{ code, msg, uuid: string, img: string(Base64) }`
3.2 登录
POST /login
- 功能:登录并颁发 `token`。
- 请求头:`isToken: false` 可选前端默认对登录不附加Authorization
- 请求体:`{ username: string, password: string, code: string, uuid: string }`
- 响应体:`{ code, msg, token: string }`(键名为常量 `Constants.TOKEN`
3.3 部门信息管理
GET /dept_info/list
- 入参:查询参数映射 `Dept` 字段(如 `name`, `manager`, `reportto` 等)
- 出参:`TableDataInfo`
GET /dept_info/{id}
- 入参:路径参数 `id`
- 出参:`AjaxResult(data: Dept)`
POST /dept_info
- 入参:`Dept` JSON
- 出参:`AjaxResult`
PUT /dept_info
- 入参:`Dept` JSON
- 出参:`AjaxResult`
DELETE /dept_info/{ids}
- 入参:路径参数,支持 `,` 分隔批量或数组映射(由后端解析为 `Long[]`
- 出参:`AjaxResult`
3.4 员工信息管理
GET /employee/list
GET /employee/{id}
POST /employee
PUT /employee
DELETE /employee/{ids}
- 说明与3.3一致;`PUT /employee` 带事务。
3.5 员工评价管理
GET /employee_kpi/list
GET /employee_kpi/{id}
POST /employee_kpi
PUT /employee_kpi
DELETE /employee_kpi/{ids}
3.6 招人名额管理
GET /hirenum/list
GET /hirenum/{id}
POST /hirenum
PUT /hirenum
DELETE /hirenum/{ids}
3.7 薪资标准管理
GET /salary_level/list
GET /salary_level/{id}
POST /salary_level
PUT /salary_level
DELETE /salary_level/{ids}
4. 数据模型(请求/响应数据结构)
- Dept: `{ id: Long, name: String, manager: String, reportto: String }`
- Employee: `{ id: Long, dept: String, name: String, position: String, salary: BigDecimal }`
- EmployeeKpi: `{ id: Long, kpi: String, bonus: String, manager: String }`
- HireNum: `{ id: Long, dept: String, num: BigDecimal, endtime: String }`
- SalaryLevel: `{ id: Long, jobType: String, salary: BigDecimal, salaryrange: BigDecimal }`
5. 安全与会话
- 登录成功后前端以Cookie键 `Admin-Token` 存储token
- 后续请求由 `@/utils/request` 在拦截器中设置 `Authorization: Bearer <token>`
- 401时前端触发登出并跳转首页。
6. 版本与变更
- 本文档随接口变更同步更新如有新增模块或字段变化请同步调整第3、4章。

@ -0,0 +1,69 @@
集成测试用例集Integration Test Cases
1. 说明
本用例集基于《体系结构设计报告》的集成测试计划细化覆盖鉴权链路与五个业务模块端到端。所有URL、字段与流程严格对应现有代码与前端调用。
2. 前置条件
- 后端服务已启动;
- `VUE_APP_BASE_API` 指向后端;
- 数据库有基础数据或可按需构造;
3. 用例列表
3.1 验证码获取
- 用例IDIT-CAP-001
- 步骤GET `/captchaImage`
- 期望:返回 `code=200` 且 `uuid` 非空,`img` 为Base64Redis含对应键值。
3.2 登录成功
- 用例IDIT-AUTH-001
- 前置:从 IT-CAP-001 获取 `uuid` 与正确验证码`code`
- 步骤POST `/login`Body含 `{username,password,code,uuid}`
- 期望:返回 `code=200` 且包含 `token` 字段(键名为后端常量)。
3.3 登录失败(验证码错误)
- 用例IDIT-AUTH-002
- 步骤POST `/login`,验证码错误
- 期望返回非200消息提示。
3.4 未认证访问受限接口
- 用例IDIT-AUTH-003
- 步骤GET `/employee/list` 不带 `Authorization`
- 期望返回401前端应提示重新登录
3.5 部门信息-分页查询
- 用例IDIT-DEPT-001
- 步骤:使用合法 `Authorization`GET `/dept_info/list` 携带查询参数
- 期望:`code=200`,返回 `TableDataInfo``rows` 长度符合分页大小。
3.6 部门信息-新增/详情/更新/删除
- 用例IDIT-DEPT-002
- 步骤:
1) POST `/dept_info` 新增;
2) GET `/dept_info/{id}` 校验详情;
3) PUT `/dept_info` 更新字段;
4) DELETE `/dept_info/{id}` 删除;
- 期望:各步骤 `code=200`,数据与数量变化正确。
3.7 员工信息-分页与CRUD
- 用例IDIT-EMP-001/002
- 步骤:同 IT-DEPT-001/002
- 特别:更新接口为事务方法,构造异常时应回滚。
3.8 员工评价-分页与CRUD
- 用例IDIT-KPI-001/002
- 步骤:同模板执行。
3.9 招人名额-分页与CRUD
- 用例IDIT-HN-001/002
- 步骤:同模板执行,关注 `num` 与 `endtime` 字段类型与格式。
3.10 薪资标准-分页与CRUD
- 用例IDIT-SAL-001/002
- 步骤:同模板执行,关注 `salary` 与 `salaryrange` 精度。
4. 通过标准
- 所有用例均返回 `code=200`(异常用例除外),数据与流程与预期一致;
- 401/错误码在前端被正确处理与提示。

@ -0,0 +1,32 @@
package com.prj.controller;
import com.prj.common.constant.Constants;
import com.prj.common.core.domain.AjaxResult;
import com.prj.common.core.domain.model.LoginBody;
import com.prj.framework.web.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LoginController
{
@Autowired
private LoginService loginService;
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody)
{
AjaxResult ajax = AjaxResult.success();
// 完成登录生成token
String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
loginBody.getUuid());
ajax.put(Constants.TOKEN, token);
return ajax;
}
}

@ -0,0 +1,2 @@
# ECMS

184
SRS

@ -0,0 +1,184 @@
软件需求规格说明书SRS
1. 引言
1.1 目的
本SRS用于规范“企业人力资源与薪酬管理系统ECMS”当前已实现的软件需求作为设计、测试与验收的依据。本文档严格依据现有代码实现与前端路由/接口编写,不包含未实现的功能。
1.2 范围
系统提供部门信息管理、员工信息管理、员工绩效评价管理、招聘名额管理、薪资标准管理、登录与验证码功能,并提供对应的前后端实现。
1.3 读者对象
开发人员、测试人员、项目管理者、指导老师与评审人员。
1.4 参考资料
- 后端:`backend/backend/prj-backendSpring Boot版` Spring Boot 应用
- 前端:`frontend` Vue 应用
- 主要代码位置:后端 `com.prj.controller.*`、`com.prj.domain.*`;前端 `src/api/*.js`、`src/router/index.js`
1.5 术语与缩略语
- KPI关键绩效指标
- SRS软件需求规格说明书
2. 系统概述
2.1 系统目标
为企业提供基础的人力资源与薪酬相关信息的增删改查与查询分页,支持登录鉴权及验证码。
2.2 用户角色
- 普通登录用户:可以访问各信息管理页面并执行相应的列表、查看、增删改操作(具体权限由后端安全配置决定,当前代码未在控制器级别区分角色)。
2.3 运行环境
- 后端Java 8+/Spring Boot接口风格为 REST
- 前端Vue使用基于 `@/utils/request` 的 Axios 封装;
- 数据库:依据实体与 MyBatis/JPA 映射(如 `HireNum` 使用 JPA 注解;其余实体通过 Mapper XML 进行 CRUD
3. 外部接口需求API
说明:以下接口均返回统一包装对象 `AjaxResult` 或分页包装 `TableDataInfo`(后端 `BaseController` 提供 `startPage()` 和 `getDataByPage()`)。
3.1 登录与验证码
- GET `/captchaImage`
- 功能获取验证码图片与UUID验证码存入 Redis键以 `Constants.CAPTCHA_CODE_KEY + uuid` 保存。
- 返回:`{ uuid: string, img: string(Base64) }` 以及成功标识。
- POST `/login`
- 入参:`{ username, password, code, uuid }`
- 功能:验证验证码并登录,成功后返回 `token`,键名为 `Constants.TOKEN`。
3.2 部门信息管理(后端 `DeptController`,前端 `src/api/dept_info.js`
- GET `/dept_info/list`:分页查询;查询参数映射到 `Dept` 字段。
- GET `/dept_info/{id}`按ID获取详情。
- POST `/dept_info`:新增部门。
- PUT `/dept_info`:修改部门。
- DELETE `/dept_info/{ids}`删除支持批量ID。
3.3 员工信息管理(后端 `EmployeeController`,前端 `src/api/employee.js`
- GET `/employee/list`:分页查询。
- GET `/employee/{id}`按ID获取详情。
- POST `/employee`:新增。
- PUT `/employee`:修改(方法含事务注解)。
- DELETE `/employee/{ids}`:删除(批量)。
3.4 员工评价管理(后端 `EmployeeKpiController`,前端 `src/api/employee_kpi.js`
- GET `/employee_kpi/list`
- GET `/employee_kpi/{id}`
- POST `/employee_kpi`
- PUT `/employee_kpi`
- DELETE `/employee_kpi/{ids}`
3.5 招人名额管理(后端 `HireNumController`,前端 `src/api/hirenum.js`
- GET `/hirenum/list`
- GET `/hirenum/{id}`
- POST `/hirenum`
- PUT `/hirenum`
- DELETE `/hirenum/{ids}`
3.6 薪资标准管理(后端 `SalaryLevelController`,前端 `src/api/salary_level.js`
- GET `/salary_level/list`
- GET `/salary_level/{id}`
- POST `/salary_level`
- PUT `/salary_level`
- DELETE `/salary_level/{ids}`
4. 功能性需求
4.1 登录与验证码
- 用户访问登录页(前端路由 `/login`),先调用 `/captchaImage` 获取 `uuid` 与验证码图片;填写用户名、密码与验证码提交到 `/login`;成功后获得 `token` 并维持会话。
4.2 菜单与路由导航(前端 `src/router/index.js`
- 首页 `/index`
- 部门信息管理 `/dept_info`
- 薪资标准管理 `/salary_level`
- 招人名额管理 `/hirenum`
- 员工信息管理 `/employee`
- 员工评价管理 `/employee_kpi`
4.3 业务模块用例(均支持列表、查看、创建、更新、删除)
- 部门信息:维护部门编号、名称、经理、汇报对象。
- 员工信息:维护员工编号、部门、姓名、职位、薪资。
- 员工评价:维护员工编号、考评结果、奖金、考评人。
- 招人名额:维护部门、名额数量、截止时间。
- 薪资标准:维护职位类型、标准薪资、上下浮动范围。
5. 数据需求(领域模型)
5.1 `Dept`
- `id: Long` 部门编号
- `name: String` 部门名
- `manager: String` 部门经理
- `reportto: String` 汇报对象
5.2 `Employee`
- `id: Long` 编号
- `dept: String` 部门
- `name: String` 姓名
- `position: String` 职位
- `salary: BigDecimal` 薪资
5.3 `EmployeeKpi`
- `id: Long` 员工编号
- `kpi: String` 考评结果
- `bonus: String` 奖金
- `manager: String` 考评人
5.4 `HireNum`带JPA表映射表名 `hire_num`
- `id: Long` 主键,自增
- `dept: String` 部门
- `num: BigDecimal` 招人名额
- `endtime: String` 截止时间
5.5 `SalaryLevel`
- `id: Long`
- `jobType: String` 职位类型
- `salary: BigDecimal` 薪资水平
- `salaryrange: BigDecimal` 上下幅度
6. 非功能性需求
6.1 性能
- 列表查询统一分页(`startPage()`),保障大数据量场景的响应;
- 常规CRUD操作在单体应用内应在300ms内完成实验室/教学环境)。
6.2 可靠性与事务
- 员工信息修改接口含事务边界,保障更新一致性;
- 其他模块使用服务层与Mapper/JPA进行持久化错误通过 `AjaxResult.error` 反馈。
6.3 安全
- 登录采用验证码 + 账号密码,颁发 `token`;前端应在请求头中附加凭证(封装于 `@/utils/request`)。
6.4 可用性
- 前端导航清晰,模块划分为部门/员工/评价/招聘名额/薪资标准五大页面 + 登录/首页。
6.5 兼容性
- 后端 REST JSON前端 Axios 调用;与现代浏览器兼容。
7. 约束与假设
- 代码中未体现复杂权限/角色控制,默认当前用户可访问相关模块;
- `HireNum` 使用JPA其余模块使用MyBatis XML见 `resources/mapper/*.xml`)。
8. 验收测试计划(概要)
8.1 登录与验证码
- 正常流程:获取验证码 → 正确填写四要素 → 返回 `token`。
- 异常流程:验证码错误/过期、账号密码错误,返回错误信息。
8.2 各模块CRUD
- 列表分页:参数查询与总数校验;
- 新增/修改:字段必填性与类型校验;
- 删除:单个与批量删除后列表与详情验证;
- 详情按ID精确返回。
8.3 前端路由与页面
- 菜单项可进入对应页面并触发相应 API
- 登录后可访问受限页面(如有拦截)。
9. 追踪矩阵(实现对齐)
- 路由 ↔ 视图 ↔ API ↔ 控制器 ↔ 领域模型
- `/dept_info` ↔ `views/dept_info` ↔ `src/api/dept_info.js` ↔ `DeptController` ↔ `Dept`
- `/employee` ↔ `views/employee` ↔ `src/api/employee.js` ↔ `EmployeeController` ↔ `Employee`
- `/employee_kpi` ↔ `views/employee_kpi` ↔ `src/api/employee_kpi.js` ↔ `EmployeeKpiController` ↔ `EmployeeKpi`
- `/hirenum` ↔ `views/hirenum` ↔ `src/api/hirenum.js` ↔ `HireNumController` ↔ `HireNum`
- `/salary_level` ↔ `views/salary_level` ↔ `src/api/salary_level.js` ↔ `SalaryLevelController` ↔ `SalaryLevel`
- `/login` ↔ `views/login` ↔ `POST /login` ↔ `LoginController`
- `/captchaImage` ↔ `GET /captchaImage` ↔ `CaptchaController`
10. 变更管理
- 本SRS将随代码与接口变更而更新当新增/调整 API、路由、数据模型时需同步修订第3~5章与追踪矩阵。
附录 A错误码
- 采用统一 `AjaxResult` 包装;具体错误码与消息由后端 `errorCode` 与全局异常处理决定(前端 `src/utils/errorCode.js`)。

@ -0,0 +1,72 @@
package com.prj.domain;
import java.math.BigDecimal;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class SalaryLevel
{
private static final long serialVersionUID = 1L;
/** 编号 */
private Long id;
/** 职位类型 */
private String jobType;
/** 薪资水平 */
private BigDecimal salary;
/** 上下幅度 */
private BigDecimal salaryrange;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setJobType(String jobType)
{
this.jobType = jobType;
}
public String getJobType()
{
return jobType;
}
public void setSalary(BigDecimal salary)
{
this.salary = salary;
}
public BigDecimal getSalary()
{
return salary;
}
public void setSalaryrange(BigDecimal salaryrange)
{
this.salaryrange = salaryrange;
}
public BigDecimal getSalaryrange()
{
return salaryrange;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("jobType", getJobType())
.append("salary", getSalary())
.append("salaryrange", getSalaryrange())
.toString();
}
}

@ -0,0 +1,76 @@
package com.prj.controller;
import java.util.List;
import com.prj.common.core.controller.BaseController;
import com.prj.common.core.domain.AjaxResult;
import com.prj.common.core.page.TableDataInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.prj.domain.SalaryLevel;
import com.prj.service.ISalaryLevelService;
@RestController
@RequestMapping("/salary_level")
public class SalaryLevelController extends BaseController
{
@Autowired
private ISalaryLevelService salaryLevelService;
/**
*
*/
@GetMapping("/list")
public TableDataInfo list(SalaryLevel salaryLevel)
{
startPage();
List<SalaryLevel> list = salaryLevelService.selectSalaryLevelList(salaryLevel);
return getDataByPage(list);
}
/**
*
*/
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return AjaxResult.success(salaryLevelService.selectSalaryLevelById(id));
}
/**
*
*/
@PostMapping
public AjaxResult add(@RequestBody SalaryLevel salaryLevel)
{
return toAjax(salaryLevelService.insertSalaryLevel(salaryLevel));
}
/**
*
*/
@PutMapping
public AjaxResult edit(@RequestBody SalaryLevel salaryLevel)
{
return toAjax(salaryLevelService.updateSalaryLevel(salaryLevel));
}
/**
*
*/
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(salaryLevelService.deleteSalaryLevelByIds(ids));
}
}

@ -0,0 +1,46 @@
单元测试计划Unit Test Plan
1. 目标
验证控制器与服务层的核心逻辑与数据契约稳定性,确保已实现功能按预期工作。
2. 范围
- 控制器:`DeptController`、`EmployeeController`、`EmployeeKpiController`、`HireNumController`、`SalaryLevelController`、`LoginController`、`CaptchaController`
- 服务:对应的 `I*Service` 及其 `impl`
3. 测试类型
- 正向用例CRUD 成功路径;
- 反向用例非法ID、缺失字段、验证码错误/过期、未携带token
- 边界场景:分页页码/大小边界、批量删除空集合/超长集合;
4. 工具与环境
- 后端Spring Boot Test + MockMvc
- 数据H2或测试库初始化测试数据或使用Mock Mapper/Repo
5. 测试用例(示例级别)
5.1 验证码
- GET /captchaImage应返回 `uuid` 与 `img(Base64)`并在Redis写入键
5.2 登录
- 正常:正确的 `{username,password,code,uuid}` → 返回 `code=200` 且包含 `token`
- 异常:验证码错误/过期 → 返回非200
5.3 部门信息管理
- list普通查询分页
- get存在ID返回详情不存在ID返回空或错误码
- add/update字段类型与业务约束验证
- delete单/多ID删除后验证数量变化
5.4 员工信息管理(含事务)
- update在事务内抛异常时应回滚数据保持一致
5.5 员工评价 / 招人名额 / 薪资标准
- 与部门/员工一致的CRUD用例模板
6. 通过标准
- 所有正向用例通过;
- 关键反向与边界用例通过;
7. 持续集成
- 建议在提交时触发单测,报告覆盖率;
Loading…
Cancel
Save