# 养老院管理系统 - 快速参考指南

## 📋 目录

1. [项目基本信息](#项目基本信息)
2. [核心技术栈](#核心技术栈)
3. [项目结构](#项目结构)
4. [关键模块](#关键模块)
5. [常用命令](#常用命令)
6. [性能问题](#性能问题)
7. [开发指南](#开发指南)
8. [常见问题](#常见问题)

---

## 项目基本信息

| 项目 | 信息 |
|------|------|
| **项目名称** | kyj-yanglao-end（养老院管理系统后端） |
| **基础框架** | 芋道 RuoYi-Vue-Pro 2.1.0 |
| **Java版本** | JDK 8+ |
| **构建工具** | Maven 3.6+ |
| **主要数据库** | MySQL 5.7+ |
| **缓存存储** | Redis 5.0+ |
| **工作流引擎** | Flowable 6.7+ |

---

## 核心技术栈

### 后端技术

```
Spring Boot 2.7.18
├─ Spring Framework 5.3.x
├─ Spring Security 5.7.x
├─ Spring Data Redis
├─ Spring Data JPA
└─ Spring Cloud (可选)

MyBatis 3.5.x
├─ MyBatis Plus 3.5.x
├─ MyBatis Generator
└─ Dynamic SQL

数据库
├─ MySQL 5.7+
├─ Oracle (可选)
├─ PostgreSQL (可选)
└─ SQL Server (可选)

缓存
├─ Redis 5.0+
├─ Spring Cache
└─ Caffeine (本地缓存)

工作流
├─ Flowable 6.7.x
└─ Activiti (可选)

其他
├─ Lombok 1.18.30
├─ MapStruct 1.5.5
├─ EasyExcel 3.x
├─ Knife4j 3.x (API文档)
├─ Hutool (工具库)
└─ Jackson (JSON处理)
```

### 前端技术

```
Vue 3 / Vue 2
├─ Vue Router
├─ Vuex / Pinia
├─ Element Plus / Element UI
├─ Axios
└─ ECharts

Vben Admin (企业级模板)
UniApp (跨平台移动)
```

---

## 项目结构

### 目录树

```
kyj-yanglao-end/
├── yudao-dependencies/           # 依赖版本管理
├── yudao-framework/              # 框架层
│   ├── yudao-common/
│   ├── yudao-spring-boot-starter-*/
│   └── pom.xml
├── yudao-module-system/          # 系统模块（核心）
│   ├── yudao-module-system-api/
│   ├── yudao-module-system-biz/
│   └── pom.xml
├── yudao-module-infra/           # 基础设施模块
├── yudao-module-member/          # 会员模块
├── yudao-module-bpm/             # 工作流模块
├── yudao-module-report/          # 报表模块
├── yudao-server/                 # 主应用
├── yudao-ui/                     # 前端项目
├── script/                       # 脚本
├── sql/                          # 数据库脚本
└── pom.xml
```

### 模块说明

| 模块 | 说明 | 状态 |
|------|------|------|
| **yudao-module-system** | 系统核心模块（长者、合同、费用、账单等） | ✅ 必需 |
| **yudao-module-infra** | 基础设施模块（文件、字典、参数等） | ✅ 必需 |
| **yudao-module-member** | 会员模块 | ⚠️ 可选 |
| **yudao-module-bpm** | 工作流模块 | ⚠️ 可选 |
| **yudao-module-report** | 报表模块 | ⚠️ 可选 |
| **yudao-module-pay** | 支付模块 | ⚠️ 可选 |

---

## 关键模块

### 1. 长者管理 (ElderlyInfo)

**主要类**:
- `ElderlyInfoDO` - 数据对象
- `ElderlyInfoMapper` - 数据访问
- `ElderlyInfoService` - 业务逻辑
- `ElderlyInfoController` - 控制器

**关键字段**:
```java
id              // 长者ID
elder_name      // 姓名
elder_sex       // 性别
elder_age       // 年龄
in_status       // 在住状态 (1=在住, 2=退住)
org_type        // 机构类型
tenant_id       // 租户ID
bed_id          // 床位ID
build_id        // 建筑ID
nurse_level_name // 护理等级
```

**常用API**:
```
GET    /api/elderly/list              # 查询列表
GET    /api/elderly/{id}              # 查询详情
POST   /api/elderly/create            # 创建
PUT    /api/elderly/update            # 更新
DELETE /api/elderly/{id}              # 删除
POST   /api/elderly/check-in          # 入住
POST   /api/elderly/check-out         # 退住
```

---

### 2. 合同管理 (ElderlyContract)

**主要类**:
- `ElderlyContractDO` - 数据对象
- `ElderlyContractMapper` - 数据访问
- `ElderlyContractService` - 业务逻辑
- `ElderlyContractController` - 控制器

**关键字段**:
```java
id              // 合同ID
elder_id        // 长者ID
contract_number // 合同号
begin_time      // 开始时间
expire_time     // 结束时间
contract_term   // 合同期限（月）
```

**常用API**:
```
GET    /api/contract/list             # 查询列表
GET    /api/contract/{id}             # 查询详情
POST   /api/contract/create           # 创建
PUT    /api/contract/update           # 更新
DELETE /api/contract/{id}             # 删除
POST   /api/contract/renew            # 续签
```

---

### 3. 费用管理 (Expense)

**主要类**:
- `ExpenseDO` - 数据对象
- `ExpenseItemDO` - 费用项数据对象
- `ExpenseMapper` - 数据访问
- `ExpenseService` - 业务逻辑
- `ExpenseController` - 控制器

**关键字段**:
```java
// Expense
id              // 费用ID
elder_id        // 长者ID
created_time    // 创建时间

// ExpenseItem
id              // 费用项ID
expense_id      // 费用ID
item_category_id // 费用分类ID
type            // 费用类型 (1=床位费, 2=护理费, 3=餐费, 4=服务费)
amount          // 金额
```

**常用API**:
```
GET    /api/expense/list              # 查询列表
GET    /api/expense/{id}              # 查询详情
POST   /api/expense/create            # 创建
PUT    /api/expense/update            # 更新
DELETE /api/expense/{id}              # 删除
GET    /api/expense/statistics        # 统计
```

---

### 4. 账单管理 (ExpenseOrder) ⚠️ 需要优化

**主要类**:
- `ExpenseOrderDO` - 数据对象
- `ExpenseOrderItemDO` - 账单项数据对象
- `ExpenseOrderMapper` - 数据访问
- `ExpenseOrderService` - 业务逻辑
- `OrderApiImpl` - API实现 **【性能问题】**
- `ExpenseOrderController` - 控制器

**关键字段**:
```java
// ExpenseOrder
id              // 账单ID
elder_id        // 长者ID
billing_month   // 账单月份 (YYYY-MM)
pay_status      // 缴费状态 (0=未缴, 1=已缴)
payable_amount  // 应付金额
actual_amount   // 实付金额
arrears_note    // 欠费备注

// ExpenseOrderItem
id              // 账单项ID
expense_order_id // 账单ID
expense_source  // 费用来源 (1=月度费用, 2=日常开支)
source_expense_item_id // 来源ID
total_amount    // 总金额
actual_price    // 实付金额
pay_status      // 缴费状态
```

**常用API**:
```
GET    /api/order/list                # 查询列表
GET    /api/order/{id}                # 查询详情
POST   /api/order/create              # 创建
PUT    /api/order/update              # 更新
DELETE /api/order/{id}                # 删除
GET    /api/order/by-month            # 按月查询
GET    /api/order/total-by-month      # 按月总计
GET    /api/order/receivable-list     # 应收列表
GET    /api/order/receivable-total    # 应收总计 【性能问题】
POST   /api/order/mark-paid           # 标记已缴
```

---

### 5. 日常开支 (DailyExpenses)

**主要类**:
- `DailyExpensesDO` - 数据对象
- `DailyExpensesMapper` - 数据访问
- `DailyExpensesService` - 业务逻辑
- `DailyExpensesController` - 控制器

**关键字段**:
```java
id              // 开支ID
elder_id        // 长者ID
item_name       // 项目名称
amount          // 金额
round_amount    // 四舍五入后金额
type            // 开支类型 (2=调整, 3=其他)
created_time    // 创建时间
```

**常用API**:
```
GET    /api/daily-expenses/list       # 查询列表
GET    /api/daily-expenses/{id}       # 查询详情
POST   /api/daily-expenses/create     # 创建
PUT    /api/daily-expenses/update     # 更新
DELETE /api/daily-expenses/{id}       # 删除
GET    /api/daily-expenses/statistics # 统计
```

---

## 常用命令

### Maven 命令

```bash
# 清理项目
mvn clean

# 编译项目
mvn compile

# 运行测试
mvn test

# 打包项目
mvn package

# 跳过测试打包
mvn package -DskipTests

# 安装到本地仓库
mvn install

# 清理并打包
mvn clean package

# 查看依赖树
mvn dependency:tree

# 更新依赖
mvn dependency:update-snapshots
```

### 应用启动

```bash
# IDE 中直接运行
# 找到 yudao-server 模块下的 YudaoServerApplication.java
# 右键 Run 或 Debug

# 命令行启动
cd yudao-server
mvn spring-boot:run

# 使用 jar 包启动
java -jar yudao-server.jar

# 指定配置文件启动
java -jar yudao-server.jar --spring.config.location=application-prod.yml
```

### 数据库初始化

```bash
# 1. 创建数据库
CREATE DATABASE yudao DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;

# 2. 执行初始化脚本
# 在 sql/mysql 目录下找到 ruoyi-vue-pro.sql
# 使用 MySQL 客户端执行

# 3. 执行定时任务脚本
# sql/mysql/quartz.sql

# 4. 验证数据库
SHOW TABLES;
SELECT COUNT(*) FROM sys_user;
```

---

## 性能问题

### 🔴 OrderApiImpl.getOrderReceivableTotal() 方法

**问题描述**:
- 循环12次调用 `getReceivableListByMonth()`
- 每次调用执行4次数据库查询
- 总共执行 **48 次数据库查询** ❌

**问题代码**:
```java
@Override
public OrderItemTotalRespVO getOrderReceivableTotal(Integer orgType, Long tenantId, String billingMonth) {
    // ...
    for (int i = 0; i < 12; i++) {
        String queryFlag = String.valueOf(i + 1);
        // 每次循环都调用一次，导致重复查询
        List<OrderItemRespVO> receivableList = getReceivableListByMonth(orgType, tenantId, queryFlag, billingMonth);
        // 处理数据...
    }
    return orderItemTotalRespVO;
}
```

**查询链路**:
```
getOrderReceivableTotal()
  └─ for i=1 to 12:
      └─ getReceivableListByMonth()
          └─ getListByMonth()
              ├─ Query 1: selectListByMonth()
              ├─ Query 2: selectList()
              ├─ Query 3: selectBatchIds()
              └─ Query 4: selectBatchIds()
```

**优化方案**:
```java
// 1. 创建 selectListByYear() 方法
List<OrderItemRespDTO> selectListByYear(Integer orgType, Long tenantId, String year);

// 2. 修改 getOrderReceivableTotal()
@Override
public OrderItemTotalRespVO getOrderReceivableTotal(Integer orgType, Long tenantId, String billingMonth) {
    // 一次性查询全年数据
    List<OrderItemRespDTO> yearData = orderMapper.selectListByYear(orgType, tenantId, year);
    
    // 在内存中按月份分组
    Map<String, List<OrderItemRespDTO>> monthlyData = yearData.stream()
        .collect(Collectors.groupingBy(OrderItemRespDTO::getBillingMonth));
    
    // 循环12个月进行累加
    for (int i = 1; i <= 12; i++) {
        String month = year + "-" + String.format("%02d", i);
        List<OrderItemRespDTO> monthItems = monthlyData.getOrDefault(month, new ArrayList<>());
        // 处理数据...
    }
    
    return orderItemTotalRespVO;
}
```

**性能提升**:
- 查询次数: 48 → 4 (减少 92%)
- 响应时间: 预计减少 80-90%

---

## 开发指南

### 添加新的业务模块

#### 步骤 1: 创建数据对象 (DO)

```java
// 在 dal/dataobject/biz 目录下创建
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("table_name")
public class MyBusinessDO extends BaseDO {
    
    @TableId
    private Long id;
    
    private String name;
    
    private BigDecimal amount;
    
    private Integer status;
    
    private Long tenantId;
    
    // ... 其他字段
}
```

#### 步骤 2: 创建 Mapper 接口

```java
// 在 dal/mysql/biz 目录下创建
@Mapper
public interface MyBusinessMapper extends BaseMapperX<MyBusinessDO> {
    
    // 自定义查询方法
    List<MyBusinessDO> selectByTenantId(@Param("tenantId") Long tenantId);
}
```

#### 步骤 3: 创建 Mapper XML

```xml
<!-- 在 resources/mapper 目录下创建 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.system.dal.mysql.biz.MyBusinessMapper">
    
    <select id="selectByTenantId" resultType="cn.iocoder.yudao.module.system.dal.dataobject.biz.MyBusinessDO">
        SELECT * FROM my_business
        WHERE tenant_id = #{tenantId}
        ORDER BY created_time DESC
    </select>
    
</mapper>
```

#### 步骤 4: 创建 Service 接口

```java
// 在 service/biz 目录下创建
public interface MyBusinessService {
    
    MyBusinessDO getById(Long id);
    
    List<MyBusinessDO> listByTenantId(Long tenantId);
    
    void create(MyBusinessDO myBusiness);
    
    void update(MyBusinessDO myBusiness);
    
    void delete(Long id);
}
```

#### 步骤 5: 创建 Service 实现

```java
// 在 service/biz 目录下创建
@Service
@Slf4j
public class MyBusinessServiceImpl implements MyBusinessService {
    
    @Resource
    private MyBusinessMapper myBusinessMapper;
    
    @Override
    public MyBusinessDO getById(Long id) {
        return myBusinessMapper.selectById(id);
    }
    
    @Override
    public List<MyBusinessDO> listByTenantId(Long tenantId) {
        return myBusinessMapper.selectByTenantId(tenantId);
    }
    
    @Override
    @Transactional
    public void create(MyBusinessDO myBusiness) {
        myBusinessMapper.insert(myBusiness);
    }
    
    @Override
    @Transactional
    public void update(MyBusinessDO myBusiness) {
        myBusinessMapper.updateById(myBusiness);
    }
    
    @Override
    @Transactional
    public void delete(Long id) {
        myBusinessMapper.deleteById(id);
    }
}
```

#### 步骤 6: 创建 Controller

```java
// 在 controller/admin/biz 目录下创建
@RestController
@RequestMapping("/api/my-business")
@Slf4j
public class MyBusinessController {
    
    @Resource
    private MyBusinessService myBusinessService;
    
    @GetMapping("/list")
    public CommonResult<List<MyBusinessRespVO>> list(
            @RequestParam Long tenantId) {
        List<MyBusinessDO> list = myBusinessService.listByTenantId(tenantId);
        return success(BeanUtils.toList(list, MyBusinessRespVO.class));
    }
    
    @GetMapping("/{id}")
    public CommonResult<MyBusinessRespVO> get(@PathVariable Long id) {
        MyBusinessDO myBusiness = myBusinessService.getById(id);
        return success(BeanUtils.toBean(myBusiness, MyBusinessRespVO.class));
    }
    
    @PostMapping("/create")
    public CommonResult<Long> create(@RequestBody MyBusinessCreateReqVO createReqVO) {
        MyBusinessDO myBusiness = BeanUtils.toBean(createReqVO, MyBusinessDO.class);
        myBusinessService.create(myBusiness);
        return success(myBusiness.getId());
    }
    
    @PutMapping("/update")
    public CommonResult<Boolean> update(@RequestBody MyBusinessUpdateReqVO updateReqVO) {
        MyBusinessDO myBusiness = BeanUtils.toBean(updateReqVO, MyBusinessDO.class);
        myBusinessService.update(myBusiness);
        return success(true);
    }
    
    @DeleteMapping("/{id}")
    public CommonResult<Boolean> delete(@PathVariable Long id) {
        myBusinessService.delete(id);
        return success(true);
    }
}
```

#### 步骤 7: 创建 VO 对象

```java
// 创建请求 VO
@Data
public class MyBusinessCreateReqVO {
    @NotBlank(message = "名称不能为空")
    private String name;
    
    @NotNull(message = "金额不能为空")
    private BigDecimal amount;
    
    private Integer status;
}

// 创建响应 VO
@Data
public class MyBusinessRespVO {
    private Long id;
    private String name;
    private BigDecimal amount;
    private Integer status;
    private LocalDateTime createdTime;
}
```

---

## 常见问题

### Q1: 如何添加新的权限？

**A**: 
1. 在 `sys_menu` 表中添加菜单记录
2. 在 `sys_role_menu` 表中关联角色和菜单
3. 使用 `@PreAuthorize("@ss.hasPermi('system:user:list')")` 注解

```java
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public CommonResult<List<UserRespVO>> list() {
    // ...
}
```

### Q2: 如何实现多租户隔离？

**A**: 
系统已内置多租户支持，自动隔离数据：

```java
// 在 Service 中自动获取当前租户
Long tenantId = TenantContextHolder.getTenantId();

// 在查询时添加租户条件
List<MyBusinessDO> list = myBusinessMapper.selectList(
    new LambdaQueryWrapperX<MyBusinessDO>()
        .eq(MyBusinessDO::getTenantId, tenantId)
);
```

### Q3: 如何添加缓存？

**A**:
```java
@Service
public class MyBusinessServiceImpl implements MyBusinessService {
    
    @Resource
    private MyBusinessMapper myBusinessMapper;
    
    @Cacheable(value = "myBusiness", key = "#id")
    @Override
    public MyBusinessDO getById(Long id) {
        return myBusinessMapper.selectById(id);
    }
    
    @CacheEvict(value = "myBusiness", key = "#myBusiness.id")
    @Override
    @Transactional
    public void update(MyBusinessDO myBusiness) {
        myBusinessMapper.updateById(myBusiness);
    }
}
```

### Q4: 如何处理事务？

**A**:
```java
@Service
public class MyBusinessServiceImpl implements MyBusinessService {
    
    @Transactional
    public void complexOperation() {
        // 多个数据库操作
        // 如果任何一个失败，都会回滚
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void operationWithException() throws Exception {
        // 指定回滚异常类型
    }
}
```

### Q5: 如何进行日志记录？

**A**:
```java
@Service
@Slf4j
public class MyBusinessServiceImpl implements MyBusinessService {
    
    public void create(MyBusinessDO myBusiness) {
        log.info("创建业务对象: {}", myBusiness.getId());
        try {
            myBusinessMapper.insert(myBusiness);
            log.info("创建成功");
        } catch (Exception e) {
            log.error("创建失败", e);
            throw e;
        }
    }
}
```

### Q6: 如何处理异常？

**A**:
```java
@RestController
public class MyBusinessController {
    
    @GetMapping("/{id}")
    public CommonResult<MyBusinessRespVO> get(@PathVariable Long id) {
        MyBusinessDO myBusiness = myBusinessService.getById(id);
        if (myBusiness == null) {
            return fail(ErrorCodeConstants.MY_BUSINESS_NOT_FOUND);
        }
        return success(BeanUtils.toBean(myBusiness, MyBusinessRespVO.class));
    }
}
```

### Q7: 如何进行单元测试？

**A**:
```java
@SpringBootTest
public class MyBusinessServiceTest {
    
    @Resource
    private MyBusinessService myBusinessService;
    
    @Test
    public void testGetById() {
        MyBusinessDO myBusiness = myBusinessService.getById(1L);
        assertNotNull(myBusiness);
        assertEquals("test", myBusiness.getName());
    }
}
```

### Q8: 如何导出数据？

**A**:
```java
@PostMapping("/export")
public void export(@RequestBody MyBusinessExportReqVO exportReqVO,
                   HttpServletResponse response) throws IOException {
    List<MyBusinessDO> list = myBusinessService.list(exportReqVO);
    List<MyBusinessExcelVO> excelList = BeanUtils.toList(list, MyBusinessExcelVO.class);
    
    EasyExcel.write(response.getOutputStream(), MyBusinessExcelVO.class)
        .sheet("数据")
        .doWrite(excelList);
}
```

### Q9: 如何导入数据？

**A**:
```java
@PostMapping("/import")
public CommonResult<Boolean> importData(@RequestParam("file") MultipartFile file) throws IOException {
    List<MyBusinessExcelVO> list = EasyExcel.read(file.getInputStream())
        .head(MyBusinessExcelVO.class)
        .sheet()
        .doReadSync();
    
    for (MyBusinessExcelVO excelVO : list) {
        MyBusinessDO myBusiness = BeanUtils.toBean(excelVO, MyBusinessDO.class);
        myBusinessService.create(myBusiness);
    }
    
    return success(true);
}
```

### Q10: 如何调试应用？

**A**:
```
1. 在 IDE 中设置断点
2. 右键选择 Debug 运行
3. 使用 F6 (单步执行) 或 F8 (继续执行)
4. 在 Variables 窗口查看变量值
5. 在 Console 窗口查看日志输出
```

---

## 📞 联系方式

- **项目地址**: https://github.com/YunaiV/ruoyi-vue-pro
- **文档地址**: https://doc.iocoder.cn
- **问题反馈**: 提交 Issue 或 Pull Request

---

**文档生成时间**: 2025-12-08
**文档版本**: 1.0
**作者**: AI Assistant






































































