Project Overview
The rapid evolution of information technology has driven the transition from traditional manual record-keeping to centralized, software-driven data management. This University Laboratory Information Management System was developed to adress the need for efficient handling of large volumes of lab-related data. By leveraging robust frameworks and modern development practices, the application streamlines administrative workflows, ensuring data normalization, operational automation, and enhanced security for institutional resources.
Development Environment
- Programming Language: Java
- Backend Framework: Spring Boot
- JDK Version: JDK 1.8
- Application Server: Tomcat 7
- Database: MySQL 5.7 (Strictly required)
- Database Client: Navicat 11
- IDE: Eclipse / IntelliJ IDEA
- Build Tool: Maven 3.3.9
- Browser: Google Chrome
Access Paths:
- Admin Portal:
localhost:8080/{project_name}/admin/dist/index.html - Front Portal:
localhost:8080/{project_name}/front/dist/index.html
Default Credentials: Username: admin / Password: admin
Technology Stack Introduction
Java
Java is a statically-typed, object-oriented programming language renowned for its multithreading capabilities and platform independence. Its modular architecture allows developers to break down complex systems into self-contained, highly cohesive modules. Java excels in enterprise environments due to its robust memory management, automatic garbage collection, and comprehensive exception handling. Furthermore, its rich standard library includes built-in networking interfaces, making it highlly suitable for developing distributed web applications.
Spring Boot Framework
Spring Boot revolutionized Java backend development by eliminating the boilerplate configuration associated with traditional Spring applications. It retains the core features of the Spring ecosystem while introducing auto-configuration. By automatically resolving dependencies and pre-configuring components at startup, Spring Boot drastically reduces development time. It also embeds web servers like Tomcat directly into the project, simplifying deployment and dependency management.
MySQL Database
MySQL is a widely adopted Relational Database Management System (RDBMS) known for its high performance, reliability, and ease of use. It utilizes a non-procedural structured query language that allows for efficient data manipulation and schema definition. Compared to its peers, MySQL stands out due to its fast query execution, low deployment cost, and strong security mechanisms, including data encryption and granulated user access control. Its support for high concurrency and low redundancy makes it an ideal choice for web-based management systems.
Core Implementation
File Storage Controller
This controller manages file uploads and downloads, utilizing Java NIO for path resolution and UUID for generating collision-free filenames.
package com.lab.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.lab.annotation.SkipAuth;
import com.lab.pojo.SystemSetting;
import com.lab.service.SettingService;
import com.lab.utils.ResponseResult;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
@RestController
@RequestMapping("/api/media")
public class MediaStorageController {
@Autowired
private SettingService settingService;
private static final String UPLOAD_DIR = "static/uploads";
private static final String PROFILE_IMAGE_KEY = "profileImage";
@PostMapping("/upload")
@SkipAuth
public ResponseResult<String> handleFileUpload(@RequestParam("document") MultipartFile document,
@RequestParam(required = false) String category) throws IOException {
if (document.isEmpty()) {
throw new IllegalArgumentException("Uploaded document cannot be empty");
}
String originalName = document.getOriginalFilename();
String extension = originalName.substring(originalName.lastIndexOf("."));
String uniqueFileName = UUID.randomUUID().toString() + extension;
Path uploadPath = Paths.get(UPLOAD_DIR);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
Path destination = uploadPath.resolve(uniqueFileName);
Files.copy(document.getInputStream(), destination);
if ("profile".equalsIgnoreCase(category)) {
SystemSetting setting = settingService.findByKey(PROFILE_IMAGE_KEY);
if (setting == null) {
setting = new SystemSetting();
setting.setConfigKey(PROFILE_IMAGE_KEY);
setting.setConfigValue(uniqueFileName);
settingService.save(setting);
} else {
setting.setConfigValue(uniqueFileName);
settingService.update(setting);
}
}
return ResponseResult.success(uniqueFileName);
}
@GetMapping("/download")
@SkipAuth
public ResponseEntity<byte[]> retrieveFile(@RequestParam String filename) {
try {
Path filePath = Paths.get(UPLOAD_DIR).resolve(filename);
if (Files.exists(filePath)) {
byte[] fileContent = Files.readAllBytes(filePath);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", filename);
return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
}
} catch (IOException e) {
e.printStackTrace();
}
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Discussion Board Controller
This controller handles CRUD operations for the forum module, implementing a recursive tree-building method to structure nested comments.
package com.lab.controller;
import com.lab.annotation.SkipAuth;
import com.lab.pojo.DiscussionPost;
import com.lab.service.DiscussionService;
import com.lab.utils.PageResult;
import com.lab.utils.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
import java.util.*;
@RestController
@RequestMapping("/api/discussions")
public class DiscussionBoardController {
@Autowired
private DiscussionService discussionService;
@GetMapping("/admin/page")
public ResponseResult<PageResult> getAdminPosts(@RequestParam Map<String, Object> queryParams,
DiscussionPost postFilter,
HttpSession session) {
if (!"ADMIN".equals(session.getAttribute("userRole"))) {
postFilter.setUserId((Long) session.getAttribute("currentUserId"));
}
PageResult result = discussionService.fetchPaginated(queryParams, postFilter);
return ResponseResult.success(result);
}
@GetMapping("/public/list")
@SkipAuth
public ResponseResult<PageResult> getPublicPosts(@RequestParam Map<String, Object> queryParams,
DiscussionPost postFilter) {
PageResult result = discussionService.fetchPaginated(queryParams, postFilter);
return ResponseResult.success(result);
}
@GetMapping("/tree/{postId}")
@SkipAuth
public ResponseResult<DiscussionPost> getPostTree(@PathVariable Long postId) {
DiscussionPost rootPost = discussionService.findById(postId);
assembleCommentTree(rootPost);
return ResponseResult.success(rootPost);
}
private void assembleCommentTree(DiscussionPost parentNode) {
List<DiscussionPost> replies = discussionService.findReplies(parentNode.getId());
if (replies == null || replies.isEmpty()) {
return;
}
parentNode.setReplies(replies);
for (DiscussionPost reply : replies) {
assembleCommentTree(reply);
}
}
@PostMapping("/create")
public ResponseResult<Void> createPost(@RequestBody DiscussionPost post, HttpSession session) {
post.setUserId((Long) session.getAttribute("currentUserId"));
discussionService.addPost(post);
return ResponseResult.success();
}
@PutMapping("/modify")
public ResponseResult<Void> modifyPost(@RequestBody DiscussionPost post) {
discussionService.updatePost(post);
return ResponseResult.success();
}
@DeleteMapping("/remove")
public ResponseResult<Void> removePosts(@RequestBody List<Long> postIds) {
discussionService.batchDelete(postIds);
return ResponseResult.success();
}
}
System Testing Strategy
To ensure system reliability, both white-box and black-box testing methodologies were employed. White-box testing validated the internal logic flows and code structures, while black-box testing focused on verifying functional requirements from an end-user perspective. Adhering to the Pareto principle, testing efforts were concentrated on the most critical and error-prone modules which constitute the core functionality. Test cases were systematically designed early in the development lifecycle to maximize logic coverage and ensure the application behaves securely and predictably under various boundary conditions.