| 项目 | 信息 |
|---|---|
| 项目名称 | 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 | 支付模块 | ⚠️ 可选 |
主要类:
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 # 退住
主要类:
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 # 续签
主要类:
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 # 统计
主要类:
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 # 标记已缴
主要类:
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 # 统计
# 清理项目
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;
问题描述:
getReceivableListByMonth()问题代码:
@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;
}
性能提升:
// 在 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;
// ... 其他字段
}
// 在 dal/mysql/biz 目录下创建
@Mapper
public interface MyBusinessMapper extends BaseMapperX<MyBusinessDO> {
// 自定义查询方法
List<MyBusinessDO> selectByTenantId(@Param("tenantId") Long tenantId);
}
<!-- 在 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>
// 在 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);
}
// 在 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);
}
}
// 在 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);
}
}
// 创建请求 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;
}
A:
sys_menu 表中添加菜单记录sys_role_menu 表中关联角色和菜单@PreAuthorize("@ss.hasPermi('system:user:list')") 注解@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public CommonResult<List<UserRespVO>> list() {
// ...
}
A: 系统已内置多租户支持,自动隔离数据:
// 在 Service 中自动获取当前租户
Long tenantId = TenantContextHolder.getTenantId();
// 在查询时添加租户条件
List<MyBusinessDO> list = myBusinessMapper.selectList(
new LambdaQueryWrapperX<MyBusinessDO>()
.eq(MyBusinessDO::getTenantId, tenantId)
);
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);
}
}
A:
@Service
public class MyBusinessServiceImpl implements MyBusinessService {
@Transactional
public void complexOperation() {
// 多个数据库操作
// 如果任何一个失败,都会回滚
}
@Transactional(rollbackFor = Exception.class)
public void operationWithException() throws Exception {
// 指定回滚异常类型
}
}
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;
}
}
}
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));
}
}
A:
@SpringBootTest
public class MyBusinessServiceTest {
@Resource
private MyBusinessService myBusinessService;
@Test
public void testGetById() {
MyBusinessDO myBusiness = myBusinessService.getById(1L);
assertNotNull(myBusiness);
assertEquals("test", myBusiness.getName());
}
}
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);
}
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);
}
A:
1. 在 IDE 中设置断点
2. 右键选择 Debug 运行
3. 使用 F6 (单步执行) 或 F8 (继续执行)
4. 在 Variables 窗口查看变量值
5. 在 Console 窗口查看日志输出
文档生成时间: 2025-12-08 文档版本: 1.0 作者: AI Assistant