Engineering a Laboratory Equipment Monitoring System with Spring Boot and Vue.js
Technology Stack Overview
The system architecture leverages specific frameworks to ensure scalability and maintainability. Spring Boot serves as the backend foundation, embedding containers like Tomcat or Jetty directly. This eliminates external configuration overhead. Key features include automatic bean assembly based on classpath dependencies and extensive starter packages for security (Spring Security) and data access (Spring Data). This reduces boilerplate code significantly.
On the frontend, Vue.js utilizes a virtual DOM to optimize rendering performence. The framework employs reactive data binding, ensuring UI components update instantly when state changes occur. Component-based architecture promotes reusability and modularity in the user interface layer.
Data persistence is handled by MyBatis-Plus, an enhancement of the classic MyBatis framework. It provides a powerful Object-Relational Mapping (ORM) capability, reducing manual SQL writing. Features include built-in pagination, dynamic query builders via lambdas, and automated CRUD operations. A code generation utility is also available to create entity classes and mapper interfaces from database schemas.
Testing Strategy
Comprehensive testing ensures the reliability of the laboratory equipment management platform. The primary objective is black-box testing, focusing on functional correctness without inspecting internal code structures. Test cases are designed to validate requirement adherence and identify logic flaws before deployment.
Authentication Validation
The login module verifies credentials against stored records and enforces session management. Inputs such as username, password, and CAPTCHA must match exact criteria. Incorrect inputs trigger specific error messages with out exposing sensitive system details.
| Input Scenario | Expected Outcome | Actual Outcome | Analysis |
|---|---|---|---|
| Valid Credentials (Admin) | Successful Access | System Granted | Pass |
| Invalid Password | Access Denied | Error Message Shown | Pass |
| Invalid Captcha | Access Denied | Verification Failed | Pass |
| Empty Username | Field Required | Validation Error | Pass |
| Admin Role Mismatch | Unauthorized | Permission Rejected | Pass |
User Management Verification
Standard operations such as creation, modification, deletion, and retrieval require strict validation. Null checks prevent incomplete records, while uniqueness constraints avoid duplicate entries. Deletion actions confirm intent via pop-up prompts.
| Input Scenario | Expected Outcome | Actual Outcome | Analysis |
|---|---|---|---|
| Complete New Record | Created Successfully | Listed in Database | Pass |
| Update Existing Record | Information Updated | Profile Changed Correctly | Pass |
| Delete Selection | Removal Confirmation | Record Purged After Confirm | Pass |
| Duplicate Entry Attempt | Creation Blocked | "Exists" Warning Triggered | Pass |
| Missing Name Field | Null Check | "Required" Prompt Displayed | Pass |
Core Implementation Details
Authentication Controller
The folloiwng snippet demonstrates the endpoint for handling secure sign-ins. It generates session tokens upon successful verification.
@PostMapping("/auth/sign-in")
public ApiResponse authenticate(AuthRequest req, HttpServletRequest request) {
// Query account using Lambda Query Wrapper
AccountEntity account = accountMapper.selectOne(
new LambdaQueryWrapper<AccountEntity>()
.eq(AccountEntity::getUsername, req.getUsername())
);
if (account == null || !passwordEncoder.matches(req.getPassword(), account.getPassword())) {
return ApiResponse.error(401, "Invalid credentials");
}
// Generate unique JWT-like token
String generatedToken = tokenService.createSession(account.getId(), account.getUsername(), "users", account.getRole());
return ApiResponse.ok(Map.of("token", generatedToken));
}
Token Generation Service
This service manages session lifecycle, including expiration and storage updates.
@Service
public class SessionServiceImpl implements SessionService {
@Override
public String createSession(Long userId, String username, String tableName, String role) {
String key = generateUniqueKey();
Calendar expireTime = Calendar.getInstance();
expireTime.add(Calendar.HOUR_OF_DAY, 2);
// Check existing session first
SessionRecord record = sessionMapper.selectOne(
new LambdaQueryWrapper<SessionRecord>()
.eq(SessionRecord::getUserid, userId)
);
if (record != null) {
record.setToken(key);
record.setExpiration(expireTime.getTime());
sessionMapper.updateById(record);
} else {
SessionRecord newRecord = new SessionRecord(userId, username, tableName, role, key, expireTime.getTime());
sessionMapper.insert(newRecord);
}
return key;
}
private String generateUniqueKey() {
return UUID.randomUUID().toString().replace("-", "").substring(0, 32);
}
}
Authorization Interceptor
Middleware handles request validation and CORS configuration to support cross-origin frontend requests.
@Component
public class AuthInterceptor implements HandlerInterceptor {
public static final String TOKEN_HEADER = "Authorization";
@Autowired
private TokenService tokenValidator;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Configure CORS Headers
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE");
response.setHeader("Access-Control-Allow-Headers", "*" );
// Handle Preflight Requests
if (RequestMethod.OPTIONS.name().equals(request.getMethod())) {
return true;
}
// Skip auth for public endpoints
RequestMappingInfo mapping = ((RequestMappingHandlerAdapter) handler).getHandlerMappings();
// Logic to check IgnoreAuth annotation would go here
String inputToken = request.getHeader(TOKEN_HEADER);
if (StringUtils.isBlank(inputToken)) {
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print(JSONObject.toJSONString(ApiResponse.error(401, "Login required")));
return false;
}
SessionRecord record = tokenValidator.validateToken(inputToken);
if (record == null) {
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print(JSONObject.toJSONString(ApiResponse.error(401, "Invalid Session")));
return false;
}
request.getSession().setAttribute("userId", record.getUserid());
request.getSession().setAttribute("role", record.getRole());
return true;
}
}
Database Schema Definition
Persistence relies on a relational structure optimized for transactional integrity. The example below defines the session storage table used for authentication tracking.
CREATE TABLE `session_tokens` (
`tid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
`user_identifier` bigint(20) NOT NULL COMMENT 'User ID Reference',
`account_name` varchar(100) NOT NULL COMMENT 'Account Username',
`target_table` varchar(100) DEFAULT NULL COMMENT 'Associated Table Name',
`permission_level` varchar(100) DEFAULT NULL COMMENT 'User Role',
`access_credential` varchar(200) NOT NULL COMMENT 'Session Token Hash',
`creation_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Generated Time',
`expiry_timestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Session Expiry',
PRIMARY KEY (`tid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='Active Sessions';
-- Sample Records
INSERT INTO `session_tokens` VALUES ('1', '101', 'lab_admin', 'users', 'administrator', 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6', '2024-01-15 10:00:00', '2024-01-15 12:00:00');
INSERT INTO `session_tokens` VALUES ('2', '102', 'student_01', 'equipment_logs', 'student', 'q7w8e9r0t1y2u3i4o5p6a7s8d9f0g1h2', '2024-01-16 09:30:00', '2024-01-16 11:30:00');