SpringBoot3+Vue3+微信小程序医院预约挂号系统开发实战

发布时间:2026/7/19 3:49:01
SpringBoot3+Vue3+微信小程序医院预约挂号系统开发实战 在医疗信息化快速发展的今天医院预约挂号系统已成为提升医疗服务效率的重要工具。传统的现场排队挂号方式不仅耗费患者大量时间也给医院管理带来巨大压力。基于SpringBoot3Vue3微信小程序的医院预约挂号系统正是为了解决这一痛点而设计的现代化解决方案。本文将完整介绍如何从零搭建一个功能完善的医院预约挂号系统涵盖后端API开发、前端管理界面实现以及微信小程序患者端开发。通过本教程你将掌握SpringBoot3与Vue3的最新特性应用以及微信小程序与后端系统的完整对接流程。1. 系统架构与技术选型1.1 整体架构设计医院预约挂号系统采用前后端分离架构后端提供RESTful API接口前端分为管理后台和微信小程序两个客户端。这种架构有利于系统扩展和维护同时能够充分利用各平台的优势。系统架构组成后端服务SpringBoot3 MyBatis-Plus MySQL Redis管理后台Vue3 Element Plus Vite微信小程序原生小程序框架 Vant Weapp部署环境Docker Nginx1.2 技术栈优势分析SpringBoot3的优势原生支持Java17性能提升显著改进的自动配置机制简化开发流程更好的GraalVM原生镜像支持增强的安全特性和监控能力Vue3的核心改进Composition API提供更好的逻辑复用更小的打包体积和更快的渲染速度更好的TypeScript支持改进的响应式系统微信小程序的特点无需下载安装即用即走原生体验性能优秀强大的社交分享能力完善的支付生态2. 开发环境准备2.1 后端开发环境JDK环境配置# 检查Java版本 java -version # 应该显示Java 17或更高版本 # 设置JAVA_HOME环境变量 export JAVA_HOME/path/to/java17 export PATH$JAVA_HOME/bin:$PATHMaven配置!-- pom.xml中的基础配置 -- properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding spring-boot.version3.0.0/spring-boot.version /properties数据库环境-- 创建数据库 CREATE DATABASE hospital_booking CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 创建用户并授权 CREATE USER hospital_user% IDENTIFIED BY secure_password; GRANT ALL PRIVILEGES ON hospital_booking.* TO hospital_user%; FLUSH PRIVILEGES;2.2 前端开发环境Node.js环境# 使用nvm管理Node版本 nvm install 18.0.0 nvm use 18.0.0 # 检查Node和npm版本 node --version npm --versionVue项目初始化# 使用Vite创建Vue3项目 npm create vuelatest hospital-admin cd hospital-admin npm install # 安装必要依赖 npm install element-plus element-plus/icons-vue npm install axios vue-router4 pinia微信小程序开发工具下载并安装微信开发者工具申请小程序AppID测试阶段可使用测试号配置项目路径和基础设置3. 数据库设计与建模3.1 核心表结构设计医院科室表departmentCREATE TABLE department ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 科室ID, name VARCHAR(100) NOT NULL COMMENT 科室名称, description TEXT COMMENT 科室描述, parent_id BIGINT DEFAULT 0 COMMENT 父科室ID, sort_order INT DEFAULT 0 COMMENT 排序序号, status TINYINT DEFAULT 1 COMMENT 状态0-停用1-启用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_parent_id (parent_id), INDEX idx_status (status) ) COMMENT医院科室表;医生信息表doctorCREATE TABLE doctor ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 医生ID, name VARCHAR(50) NOT NULL COMMENT 医生姓名, department_id BIGINT NOT NULL COMMENT 所属科室ID, title VARCHAR(50) COMMENT 职称, specialty TEXT COMMENT 专业特长, photo_url VARCHAR(500) COMMENT 照片URL, introduction TEXT COMMENT 医生介绍, work_experience INT COMMENT 工作年限, status TINYINT DEFAULT 1 COMMENT 状态0-停诊1-出诊, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_department_id (department_id), INDEX idx_status (status) ) COMMENT医生信息表;排班信息表scheduleCREATE TABLE schedule ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 排班ID, doctor_id BIGINT NOT NULL COMMENT 医生ID, work_date DATE NOT NULL COMMENT 工作日期, time_slot TINYINT NOT NULL COMMENT 时间段1-上午2-下午3-晚上, total_count INT DEFAULT 30 COMMENT 总号源数, booked_count INT DEFAULT 0 COMMENT 已预约数, status TINYINT DEFAULT 1 COMMENT 状态0-停诊1-出诊, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_doctor_date_slot (doctor_id, work_date, time_slot), INDEX idx_work_date (work_date), INDEX idx_status (status) ) COMMENT医生排班表;3.2 业务关系设计预约订单表booking_orderCREATE TABLE booking_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 订单ID, order_no VARCHAR(64) UNIQUE NOT NULL COMMENT 订单编号, patient_id BIGINT NOT NULL COMMENT 患者ID, schedule_id BIGINT NOT NULL COMMENT 排班ID, doctor_id BIGINT NOT NULL COMMENT 医生ID, department_id BIGINT NOT NULL COMMENT 科室ID, booking_date DATE NOT NULL COMMENT 预约日期, time_slot TINYINT NOT NULL COMMENT 时间段, patient_name VARCHAR(50) NOT NULL COMMENT 患者姓名, patient_phone VARCHAR(20) NOT NULL COMMENT 患者电话, id_card VARCHAR(18) COMMENT 身份证号, symptoms TEXT COMMENT 症状描述, order_status TINYINT DEFAULT 0 COMMENT 订单状态0-待支付1-已预约2-已取消3-已完成, amount DECIMAL(10,2) DEFAULT 0.00 COMMENT 订单金额, pay_time DATETIME COMMENT 支付时间, cancel_time DATETIME COMMENT 取消时间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_patient_id (patient_id), INDEX idx_schedule_id (schedule_id), INDEX idx_order_status (order_status), INDEX idx_booking_date (booking_date) ) COMMENT预约订单表;4. SpringBoot3后端核心实现4.1 项目结构规划src/main/java/com/hospital/booking/ ├── config/ # 配置类 ├── controller/ # 控制器层 ├── service/ # 业务逻辑层 ├── mapper/ # 数据访问层 ├── entity/ # 实体类 ├── dto/ # 数据传输对象 ├── vo/ # 视图对象 ├── common/ # 通用工具类 └── HospitalBookingApplication.java4.2 核心依赖配置!-- pom.xml关键依赖 -- dependencies !-- SpringBoot3 Starter -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 数据库相关 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- 工具类 -- dependency groupIdorg.apache.commons/groupId artifactIdcommons-lang3/artifactId /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency /dependencies4.3 医生排班管理实现实体类定义// entity/Schedule.java Data TableName(schedule) public class Schedule { TableId(type IdType.AUTO) private Long id; private Long doctorId; private LocalDate workDate; private Integer timeSlot; private Integer totalCount; private Integer bookedCount; private Integer status; TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; // 关联医生信息 TableField(exist false) private String doctorName; TableField(exist false) private String departmentName; }业务逻辑层// service/ScheduleService.java Service public class ScheduleService { Autowired private ScheduleMapper scheduleMapper; Autowired private RedisTemplateString, Object redisTemplate; /** * 生成医生排班 */ public void generateSchedule(ScheduleGenerateDTO generateDTO) { // 验证参数 if (generateDTO.getStartDate().isAfter(generateDTO.getEndDate())) { throw new BusinessException(开始日期不能晚于结束日期); } // 生成排班数据 LocalDate currentDate generateDTO.getStartDate(); while (!currentDate.isAfter(generateDTO.getEndDate())) { for (Long doctorId : generateDTO.getDoctorIds()) { for (Integer timeSlot : generateDTO.getTimeSlots()) { Schedule schedule new Schedule(); schedule.setDoctorId(doctorId); schedule.setWorkDate(currentDate); schedule.setTimeSlot(timeSlot); schedule.setTotalCount(30); // 默认30个号源 schedule.setBookedCount(0); schedule.setStatus(1); // 检查是否已存在排班 LambdaQueryWrapperSchedule queryWrapper new LambdaQueryWrapper(); queryWrapper.eq(Schedule::getDoctorId, doctorId) .eq(Schedule::getWorkDate, currentDate) .eq(Schedule::getTimeSlot, timeSlot); if (scheduleMapper.selectCount(queryWrapper) 0) { scheduleMapper.insert(schedule); } } } currentDate currentDate.plusDays(1); } } /** * 查询可预约的排班 */ public PageResultScheduleVO getAvailableSchedules(ScheduleQueryDTO queryDTO) { PageSchedule page new Page(queryDTO.getPageNum(), queryDTO.getPageSize()); LambdaQueryWrapperSchedule queryWrapper new LambdaQueryWrapper(); queryWrapper.eq(Schedule::getStatus, 1) .ge(Schedule::getWorkDate, LocalDate.now()) .lt(Schedule::getWorkDate, LocalDate.now().plusDays(8)) // 未来7天 .apply(booked_count total_count); // 还有号源 if (queryDTO.getDepartmentId() ! null) { // 关联查询医生所属科室 queryWrapper.inSql(Schedule::getDoctorId, SELECT id FROM doctor WHERE department_id queryDTO.getDepartmentId()); } if (queryDTO.getWorkDate() ! null) { queryWrapper.eq(Schedule::getWorkDate, queryDTO.getWorkDate()); } PageSchedule schedulePage scheduleMapper.selectPage(page, queryWrapper); // 转换为VO并填充关联信息 ListScheduleVO scheduleVOS schedulePage.getRecords().stream() .map(this::convertToVO) .collect(Collectors.toList()); return new PageResult(scheduleVOS, schedulePage.getTotal()); } }4.4 预约订单处理订单服务核心逻辑// service/BookingOrderService.java Service Transactional public class BookingOrderService { Autowired private BookingOrderMapper bookingOrderMapper; Autowired private ScheduleMapper scheduleMapper; Autowired private RedisTemplateString, Object redisTemplate; /** * 创建预约订单 */ public BookingOrder createOrder(BookingOrderCreateDTO createDTO) { // 使用Redis分布式锁防止重复提交 String lockKey booking_lock: createDTO.getScheduleId() : createDTO.getPatientPhone(); Boolean lockAcquired redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, Duration.ofSeconds(30)); if (Boolean.FALSE.equals(lockAcquired)) { throw new BusinessException(操作过于频繁请稍后重试); } try { // 验证排班信息 Schedule schedule scheduleMapper.selectById(createDTO.getScheduleId()); if (schedule null || schedule.getStatus() 0) { throw new BusinessException(该排班不存在或已停诊); } if (schedule.getBookedCount() schedule.getTotalCount()) { throw new BusinessException(该时段号源已满); } // 检查是否重复预约 LambdaQueryWrapperBookingOrder queryWrapper new LambdaQueryWrapper(); queryWrapper.eq(BookingOrder::getPatientPhone, createDTO.getPatientPhone()) .eq(BookingOrder::getScheduleId, createDTO.getScheduleId()) .in(BookingOrder::getOrderStatus, Arrays.asList(0, 1)); // 待支付或已预约 if (bookingOrderMapper.selectCount(queryWrapper) 0) { throw new BusinessException(您已预约该时段请勿重复操作); } // 创建订单 BookingOrder order new BookingOrder(); order.setOrderNo(generateOrderNo()); order.setPatientId(createDTO.getPatientId()); order.setScheduleId(createDTO.getScheduleId()); order.setDoctorId(schedule.getDoctorId()); order.setDepartmentId(createDTO.getDepartmentId()); order.setBookingDate(schedule.getWorkDate()); order.setTimeSlot(schedule.getTimeSlot()); order.setPatientName(createDTO.getPatientName()); order.setPatientPhone(createDTO.getPatientPhone()); order.setIdCard(createDTO.getIdCard()); order.setSymptoms(createDTO.getSymptoms()); order.setOrderStatus(0); // 待支付 order.setAmount(new BigDecimal(15.00)); // 挂号费 bookingOrderMapper.insert(order); // 更新排班已预约数量 schedule.setBookedCount(schedule.getBookedCount() 1); scheduleMapper.updateById(schedule); return order; } finally { // 释放锁 redisTemplate.delete(lockKey); } } /** * 生成订单号 */ private String generateOrderNo() { String timestamp LocalDateTime.now().format(DateTimeFormatter.ofPattern(yyyyMMddHHmmss)); String random String.valueOf(ThreadLocalRandom.current().nextInt(1000, 9999)); return BOOK timestamp random; } }5. Vue3管理后台开发5.1 项目配置与路由Vite配置// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import { resolve } from path export default defineConfig({ plugins: [vue()], resolve: { alias: { : resolve(__dirname, src) } }, server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })路由配置// src/router/index.js import { createRouter, createWebHistory } from vue-router const routes [ { path: /, redirect: /dashboard }, { path: /login, component: () import(/views/Login.vue) }, { path: /dashboard, component: () import(/views/Dashboard.vue), meta: { requiresAuth: true } }, { path: /department, component: () import(/views/Department.vue), meta: { requiresAuth: true } }, { path: /doctor, component: () import(/views/Doctor.vue), meta: { requiresAuth: true } }, { path: /schedule, component: () import(/views/Schedule.vue), meta: { requiresAuth: true } }, { path: /booking, component: () import(/views/Booking.vue), meta: { requiresAuth: true } } ] const router createRouter({ history: createWebHistory(), routes }) // 路由守卫 router.beforeEach((to, from, next) { const token localStorage.getItem(admin_token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } }) export default router5.2 科室管理组件实现!-- src/views/Department.vue -- template div classdepartment-management el-card template #header div classcard-header span科室管理/span el-button typeprimary clickhandleAdd新增科室/el-button /div /template el-table :datadepartmentList v-loadingloading el-table-column propid labelID width80/el-table-column el-table-column propname label科室名称/el-table-column el-table-column propdescription label科室描述 show-overflow-tooltip/el-table-column el-table-column propsortOrder label排序 width80/el-table-column el-table-column propstatus label状态 width100 template #defaultscope el-tag :typescope.row.status ? success : danger {{ scope.row.status ? 启用 : 停用 }} /el-tag /template /el-table-column el-table-column label操作 width200 template #defaultscope el-button sizesmall clickhandleEdit(scope.row)编辑/el-button el-button sizesmall typedanger clickhandleDelete(scope.row)删除/el-button /template /el-table-column /el-table div classpagination-container el-pagination v-model:current-pagequeryParams.pageNum v-model:page-sizequeryParams.pageSize :totaltotal current-changehandlePageChange layouttotal, sizes, prev, pager, next, jumper :page-sizes[10, 20, 50, 100] / /div /el-card !-- 新增/编辑对话框 -- el-dialog :titledialogTitle v-modeldialogVisible width500px el-form :modelform :rulesrules refformRef label-width80px el-form-item label科室名称 propname el-input v-modelform.name placeholder请输入科室名称/el-input /el-form-item el-form-item label科室描述 propdescription el-input v-modelform.description typetextarea :rows3 placeholder请输入科室描述 /el-input /el-form-item el-form-item label排序 propsortOrder el-input-number v-modelform.sortOrder :min0/el-input-number /el-form-item el-form-item label状态 propstatus el-switch v-modelform.status :active-value1 :inactive-value0/el-switch /el-form-item /el-form template #footer el-button clickdialogVisible false取消/el-button el-button typeprimary clickhandleSubmit确定/el-button /template /el-dialog /div /template script setup import { ref, reactive, onMounted } from vue import { ElMessage, ElMessageBox } from element-plus import { departmentApi } from /api/department // 响应式数据 const loading ref(false) const dialogVisible ref(false) const dialogTitle ref() const departmentList ref([]) const total ref(0) const formRef ref() const queryParams reactive({ pageNum: 1, pageSize: 10 }) const form reactive({ id: null, name: , description: , sortOrder: 0, status: 1 }) const rules { name: [{ required: true, message: 请输入科室名称, trigger: blur }] } // 生命周期 onMounted(() { loadDepartmentList() }) // 方法 const loadDepartmentList async () { loading.value true try { const response await departmentApi.getList(queryParams) departmentList.value response.data.list total.value response.data.total } catch (error) { ElMessage.error(加载失败) } finally { loading.value false } } const handleAdd () { dialogTitle.value 新增科室 Object.assign(form, { id: null, name: , description: , sortOrder: 0, status: 1 }) dialogVisible.value true } const handleEdit (row) { dialogTitle.value 编辑科室 Object.assign(form, { ...row }) dialogVisible.value true } const handleDelete async (row) { try { await ElMessageBox.confirm(确定要删除该科室吗, 提示, { type: warning }) await departmentApi.deleteDepartment(row.id) ElMessage.success(删除成功) loadDepartmentList() } catch (error) { // 用户取消删除 } } const handleSubmit async () { if (!formRef.value) return await formRef.value.validate() try { if (form.id) { await departmentApi.updateDepartment(form) ElMessage.success(更新成功) } else { await departmentApi.addDepartment(form) ElMessage.success(新增成功) } dialogVisible.value false loadDepartmentList() } catch (error) { ElMessage.error(操作失败) } } const handlePageChange (page) { queryParams.pageNum page loadDepartmentList() } /script style scoped .department-management { padding: 20px; } .card-header { display: flex; justify-content: space-between; align-items: center; } .pagination-container { margin-top: 20px; text-align: right; } /style6. 微信小程序患者端开发6.1 小程序页面结构miniprogram/ ├── pages/ │ ├── index/ # 首页 │ ├── department/ # 科室列表 │ ├── doctor/ # 医生列表 │ ├── schedule/ # 排班预约 │ ├── booking/ # 预约详情 │ └── my/ # 个人中心 ├── components/ # 公共组件 ├── utils/ # 工具类 └── app.js # 小程序入口6.2 首页实现// pages/index/index.js Page({ data: { banners: [ { id: 1, image: /images/banner1.jpg, title: 在线预约挂号 }, { id: 2, image: /images/banner2.jpg, title: 专家门诊 } ], departments: [], doctors: [], loading: true }, onLoad() { this.loadHomeData() }, onPullDownRefresh() { this.loadHomeData().then(() { wx.stopPullDownRefresh() }) }, async loadHomeData() { try { this.setData({ loading: true }) const [departmentsRes, doctorsRes] await Promise.all([ wx.request({ url: https://your-api.com/api/department/top, method: GET }), wx.request({ url: https://your-api.com/api/doctor/recommend, method: GET }) ]) this.setData({ departments: departmentsRes.data.data || [], doctors: doctorsRes.data.data || [], loading: false }) } catch (error) { this.setData({ loading: false }) wx.showToast({ title: 加载失败, icon: error }) } }, // 跳转到科室页面 navigateToDepartment() { wx.navigateTo({ url: /pages/department/department }) }, // 跳转到医生页面 navigateToDoctor(e) { const doctorId e.currentTarget.dataset.id wx.navigateTo({ url: /pages/doctor/detail?id${doctorId} }) }, // 立即预约 makeAppointment() { wx.navigateTo({ url: /pages/department/department }) } })!-- pages/index/index.wxml -- view classcontainer !-- 轮播图 -- swiper classbanner-swiper indicator-dots autoplay circular swiper-item wx:for{{banners}} wx:keyid image classbanner-image src{{item.image}} modeaspectFill/image view classbanner-title{{item.title}}/view /swiper-item /swiper !-- 快捷功能 -- view classquick-actions view classaction-item bindtapnavigateToDepartment image classaction-icon src/icons/department.png/image text按科室挂号/text /view view classaction-item bindtapmakeAppointment image classaction-icon src/icons/booking.png/image text立即预约/text /view view classaction-item bindtapnavigateToMy image classaction-icon src/icons/profile.png/image text我的预约/text /view /view !-- 推荐科室 -- view classsection view classsection-header text classsection-title热门科室/text text classsection-more bindtapnavigateToDepartment查看更多 /text /view scroll-view classdepartment-scroll scroll-x view classdepartment-list view classdepartment-item wx:for{{departments}} wx:keyid bindtapnavigateToDoctorList>// pages/booking/confirm.js Page({ data: { schedule: null, patientInfo: { name: , phone: , idCard: , symptoms: }, rules: { name: [{ required: true, message: 请输入姓名 }], phone: [ { required: true, message: 请输入手机号 }, { pattern: /^1[3-9]\d{9}$/, message: 手机号格式不正确 } ] } }, onLoad(options) { this.loadScheduleDetail(options.scheduleId) this.loadPatientInfo() }, async loadScheduleDetail(scheduleId) { try { const res await wx.request({ url: https://your-api.com/api/schedule/${scheduleId}, method: GET }) this.setData({ schedule: res.data.data }) } catch (error) { wx.showToast({ title: 加载失败, icon: error }) } }, loadPatientInfo() { // 从缓存加载患者信息 const patientInfo wx.getStorageSync(patientInfo) || {} this.setData({ patientInfo: { ...this.data.patientInfo, ...patientInfo } }) }, // 表单输入处理 onInputChange(e) { const { field } e.currentTarget.dataset this.setData({ [patientInfo.${field}]: e.detail.value }) }, // 提交预约 async submitBooking() { // 表单验证 if (!this.validateForm()) { return } try { wx.showLoading({ title: 提交中... }) const res await wx.request({ url: https://your-api.com/api/booking/order, method: POST, data: { scheduleId: this.data.schedule.id, ...this.data.patientInfo } }) wx.hideLoading() if (res.data.code 200) { // 保存患者信息 wx.setStorageSync(patientInfo, this.data.patientInfo) // 跳转到支付页面 wx.navigateTo({ url: /pages/booking/payment?orderNo${res.data.data.orderNo} }) } else { wx.showToast({ title: res.data.message || 预约失败, icon: error }) } } catch (error) { wx.hideLoading() wx.showToast({ title: 网络错误, icon: error }) } }, validateForm() { const { name, phone } this.data.patientInfo if (!name.trim()) { wx.showToast({ title: 请输入姓名, icon: none }) return false } if (!phone.trim()) { wx.showToast({ title: 请输入手机号, icon: none }) return false } if (!/^1[3-9]\d{9}$/.test(phone)) { wx.showToast({ title: 手机号格式不正确, icon: none }) return false } return true } })7. 系统集成与部署7.1 后端API部署配置Dockerfile配置# 后端Dockerfile FROM openjdk:17-jdk-slim # 设置时区 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime # 创建应用目录 WORKDIR /app # 复制JAR文件 COPY target/hospital-booking-0.0.1-SNAPSHOT.jar app.jar # 暴露端口 EXPOSE 8080 # 启动应用 ENTRYPOINT [java, -jar, app.jar]Docker Compose配置# docker-compose.yml version: 3.8 services: mysql: image: mysql:8.0 container_name: hospital-mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: hospital_booking MYSQL_USER: hospital_user MYSQL_PASSWORD: user123 ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql networks: - hospital-network redis: image: redis:7.0-alpine container_name: hospital-redis ports: - 6379:6379 volumes: - redis_data:/data networks: - hospital-network backend: build: ./backend container_name: hospital-backend ports: - 8080:8080 environment: - SPRING_PROFILES_ACTIVEprod - SPRING_DATASOURCE_URLjdbc:mysql://mysql:3306/hospital_booking - SPRING_REDIS_HOSTredis depends_on: - mysql - redis networks: - hospital-network nginx: image: nginx:alpine container_name: hospital-nginx ports: - 80:80 - 443:443 volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - backend networks: - hospital-network volumes: mysql_data: redis_data: networks: hospital-network: driver: bridge7.2 前端部署配置Nginx配置