Developing a Graduation Project Management System Using Spring Boot, Vue, and Uniapp
Technology Stack
The backend is built with Spring Boot, taking advantage of its embedded server support (Tomcat, Jetty, Undertow) and auto-configuration to streamline development. This eliminates the need for manual server setup and minimizes boilerplate. On the frontend, Vue.js drives a reactive, component-based interface using a virtual DOM for efficient UI updates. Data persistence is handled by MyBatis, which separates SQL from Java code and supports dynamic query building, caching layers, and pluggable extensions.
Core Backend Example
The following is a minimal Spring Boot application exposing a REST endpoint:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class AppEntry {
public static void main(String[] args) {
SpringApplication.run(AppEntry.class, args);
}
@RequestMapping("/greet")
public String greetUser() {
return "Hello from Spring Boot!";
}
}
Frontend Reactive Binding
A basic Vue instance demonstrates data synchronization and event handling:
<div id="root">
<p>{{ displayText }}</p>
<button v-on:click="updateText">Modify</button>
</div>
<script>
var vm = new Vue({
el: '#root',
data: {
displayText: 'Original Message'
},
methods: {
updateText: function() {
this.displayText = 'Updated Content!';
}
}
});
</script>
Authentication and Token Management
User login is handled through an endpoint that validates credentials and issues a token. A custom annotation @SkipAuth marks endpoints that do not require authentication.
@PostMapping("/api/signin")
@SkipAuth
public R signIn(String username, String password, HttpServletRequest req) {
UserEntity user = userService.selectOne(
new QueryWrapper<UserEntity>().eq("username", username)
);
if (user == null || !user.getPassword().equals(password)) {
return R.error("Invalid username or password");
}
String token = tokenService.issueToken(user.getId(), username, "users", user.getRole());
return R.ok().put("token", token);
}
Token generation ensures uniqueness and sets an expiration of one hour:
@Override
public String issueToken(Long userId, String username, String table, String role) {
TokenEntity existingToken = this.selectOne(
new QueryWrapper<TokenEntity>().eq("userid", userId).eq("role", role)
);
String token = RandomStringUtils.randomAlphanumeric(32);
Calendar cal = Calendar.getInstance();
cal.add(Calendar.HOUR_OF_DAY, 1);
if (existingToken != null) {
existingToken.setTokenValue(token);
existingToken.setExpiresAt(cal.getTime());
this.updateById(existingToken);
} else {
this.insert(new TokenEntity(userId, username, table, role, token, cal.getTime()));
}
return token;
}
Request Interceptor for Authentication
All protected routes pass through an interceptor that etxracts the token from a header and validates it. If800 the token is14 absent or invalid, a 401 response is returned:
@Component
public class AuthInterceptor implements HandlerInterceptor {
public static final String HEADER_TOKEN = "Token";
@Autowired
private TokenService tokenService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Token");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(200);
return false;
}
SkipAuth skip = null;
if (handler instanceof HandlerMethod) {
skip = ((HandlerMethod) handler).getMethodAnnotation(SkipAuth.class);
} else {
return true;
}
if (skip != null) {
return true;
}
String tokenValue = request.getHeader(HEADER_TOKEN);
if (StringUtils.isBlank(tokenValue)) {
send401(response);
return false;
}
TokenEntity tokenEntity = tokenService.getByToken(tokenValue);
if (tokenEntity == null) {
send401(response);
return false;
}
request.getSession().setAttribute("userId", tokenEntity.getUserid());
request.getSession().setAttribute("role", tokenEntity.getRole());
return true;
}
private void send401(HttpServletResponse resp) throws IOException {
resp.setContentType("application/json; charset=UTF-8");
resp.getWriter().write(JSON.toJSONString(R.error(401, "Please login first")));
}
}
Database Design
A typical product table illustrates the structure used by the system:
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary key',
`name` VARCHAR(100) NOT NULL COMMENT 'Product name',
`price` DECIMAL(10,2) NOT NULL COMMENT 'Unit price',
`description` VARCHAR(200) DEFAULT NULL COMMENT 'Brief description',
`stock` INT(11) NOT NULL COMMENT 'Current stock',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Sample data isnertion:
INSERT INTO `product` (`name`, `price`, `description`, `stock`) VALUES
('Wireless Headphones', 199.99, 'Noise-cancelling over-ear model', 200),
('Mechanical Keyboard', 149.50, 'RGB backlit with blue switches', 75),
('USB-C Hub', 45.00, '7-in-1 adapter for laptops', 120);
Testing Approach
Functional testing is performed using black-box techniques, focusing on12 user flows like login and user management. Test cases cover valid inputs, boundary values,14 missing fields, and duplicate entries. For example, login tests verify13 that incorrect credentials and empty fields trigger appropriate error messages. User management tests validate adding, editing, and deleting records, ensuring consistent16 outcomes11 between expected and actual results. This process confirms that all functional requirements are met and16 the system behaves reliably under various conditions.