快速参考指南.md 20 KB

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

📋 目录

  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 - 控制器

关键字段:

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 - 控制器

关键字段:

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 - 控制器

关键字段:

// 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 - 控制器

关键字段:

// 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 - 控制器

关键字段:

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 命令

# 清理项目
mvn clean

# 编译项目
mvn compile

# 运行测试
mvn test

# 打包项目
mvn package

# 跳过测试打包
mvn package -DskipTests

# 安装到本地仓库
mvn install

# 清理并打包
mvn clean package

# 查看依赖树
mvn dependency:tree

# 更新依赖
mvn dependency:update-snapshots

应用启动

# 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

数据库初始化

# 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 次数据库查询

问题代码:

@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()

优化方案:

// 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)

// 在 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 接口

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

步骤 3: 创建 Mapper 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 接口

// 在 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 实现

// 在 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

// 在 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 对象

// 创建请求 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')") 注解
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public CommonResult<List<UserRespVO>> list() {
    // ...
}

Q2: 如何实现多租户隔离?

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

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

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

Q3: 如何添加缓存?

A:

@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:

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

Q5: 如何进行日志记录?

A:

@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:

@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:

@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:

@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:

@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 窗口查看日志输出

📞 联系方式


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