Cloud-Native Hotel Management Platform with SSM Stack

This article walks through the architecture, key modules, and representative code snippets of a production-grade hotel management system built with Spring, Spring MVC, and MyBatis (SSM). The project, internally named SeaWave Cloud, is tailored for mid-scale hotel chains that need both front-desk agility and back-office control.

  1. System Overview

SeaWave Cloud consolidates reservation, housekeeping, HR, finance, and analytics into a single web portal. Its designed to run on any servlet container (Tomcat 9+) and uses MySQL 8 as the primary datastore. The UI is a lightweight Vue3 SPA served through Nginx, but the snippets below focus on the backend only.

  1. Core Advantages

  • Single sign-on for receptionists, managers, and maintenance staff.
  • Real-time room status synchronization across POS terminals and mobile housekeeping app.
  • Batch import/export of guest lists, employee rosters, and maintenance logs via Excel.
  • Role-based dashboards—reception sees occupancy heat-maps, finance sees revenue KPIs.
  1. Technology Stack

Layer Technology
Web Spring MVC 5, Jackson, Hibernate Validator
Service Spring 5, declarative transactions
Persistence MyBatis 3.5, MyBatis-Plus, PageHelper
Security Spring Security (JWT filter, BCrypt)
Excel Alibaba EasyExcel 3.x
Logging Logback + Mapped Diagnostic Context (MDC)
  1. Module Map


┌────────────────────────────┐
│  Gateway & Auth            │ JWT filter, rate-limit
├────────────────────────────┤
│  Reservation Service       │ CRUD bookings, occupancy cache
├────────────────────────────┤
│  Room Service              │ Status, housekeeping tasks
├────────────────────────────┤
│  HR Service                │ Staff, shifts, permissions
├────────────────────────────┤
│  Report Service            │ Revenue, guest analytics
└────────────────────────────┘
  1. Screenshots

(Not included; the original images showed login, room grid, member list, reservation calendar, and F&B ordering.)

  1. Representative Code

6.1 Global Constants

public final class AppConstants {
    private AppConstants() {}

    public static final int STATUS_OK = 200;
    public static final int STATUS_ERR = 400;

    public static final int USER_ACTIVE = 1;
    public static final int USER_SUSPENDED = 2;
    public static final int USER_LOCKED = 3;

    public static final int ROLE_ADMIN = 1;
    public static final int ROLE_STAFF = 2;

    public static final String AES_SECRET = "ehfN81ZWRJf3b4s8Lf5/Vg==";
}

6.2 Maintenance Entity

@Data
public class MaintenanceTicket {
    private Integer id;
    private Integer roomNumber;
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date scheduledDate;
    private String status;        // OPEN, IN_PROGRESS, DONE
    private String roomCategory;
}

6.3 Excel DTO for Maintenance Export

@Data
public class MaintenanceExportVo {
    @ExcelProperty("Status")
    private String status;
    @ExcelProperty("Room Category")
    private String roomCategory;
}

6.4 Batch User Import Listener

@Component
public class UserBatchListener extends AnalysisEventListener<UserExcelRow> {

    @Autowired
    private UserMapper userMapper;

    private final List<User> buffer = new ArrayList<>(BATCH_SIZE);

    @Override
    public void invoke(UserExcelRow row, AnalysisContext ctx) {
        User user = new User();
        BeanUtils.copyProperties(row, user);
        user.setPassword(AesUtil.encrypt(AppConstants.AES_SECRET, row.getPassword()));
        buffer.add(user);
        if (buffer.size() >= BATCH_SIZE) {
            flush();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext ctx) {
        flush();
    }

    private void flush() {
        if (!buffer.isEmpty()) {
            userMapper.insertBatch(buffer);
            buffer.clear();
        }
    }
}

6.5 User Service (condensed)

@Service
@Transactional
public class UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private AuditLogMapper auditLogMapper;

    public ApiResp<User> login(String username, String rawPwd, HttpSession session) {
        User user = userMapper.selectByUsername(username);
        if (user == null) return ApiResp.error("Account not found");

        if (user.getStatus() != AppConstants.USER_ACTIVE)
            return ApiResp.error("Account disabled");

        String hashed = AesUtil.encrypt(AppConstants.AES_SECRET, rawPwd);
        if (!hashed.equals(user.getPassword()))
            return ApiResp.error("Invalid credentials");

        session.setAttribute("user", user);
        auditLogMapper.insert(new AuditLog(user.getId(), "LOGIN", "success"));
        return ApiResp.ok(user);
    }

    public ApiResp<Void> importUsers(MultipartFile file) {
        try {
            EasyExcel.read(file.getInputStream(), UserExcelRow.class, userBatchListener)
                     .sheet().doRead();
            return ApiResp.ok(null);
        } catch (IOException e) {
            return ApiResp.error("Upload failed");
        }
    }

    public void exportUsers(UserFilter filter, HttpServletResponse resp) throws IOException {
        resp.setHeader("Content-Disposition",
                "attachment; filename=users_" + System.currentTimeMillis() + ".xlsx");
        List<User> data = userMapper.selectByFilter(filter);
        EasyExcel.write(resp.getOutputStream(), User.class).sheet("Users").doWrite(data);
    }

    public PageResult<User> page(int current, int size) {
        PageHelper.startPage(current, size);
        PageInfo<User> info = new PageInfo<>(userMapper.selectAll());
        return new PageResult<>(info.getTotal(), info.getList());
    }
}

6.6 Uniform Response Wrapper

@Data
@AllArgsConstructor
public class ApiResp<T> {
    private int code;
    private String msg;
    private T data;

    public static <T> ApiResp<T> ok(T data) {
        return new ApiResp<>(AppConstants.STATUS_OK, "success", data);
    }

    public static ApiResp<Void> error(String msg) {
        return new ApiResp<>(AppConstants.STATUS_ERR, msg, null);
    }
}

The snippets above illustrate how constants, entities, listeners, services, and the shared response object fit together in SeaWave Cloud. Replace AesUtil with your preferred crypto utility, and swap EasyExcel for Apache POI if desired—the overall structure remains valid.

Tags: SSM Spring MVC MyBatis EasyExcel Hotel Management

Posted on Thu, 13 Aug 2026 16:33:46 +0000 by Jeyush