Fading Coder

One Final Commit for the Last Sprint

Home > Tools > Content

Developing a Graduation Project Management System Using Spring Boot, Vue, and Uniapp

Tools 1

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.

Related Articles

Efficient Usage of HTTP Client in IntelliJ IDEA

IntelliJ IDEA incorporates a versatile HTTP client tool, enabling developres to interact with RESTful services and APIs effectively with in the editor. This functionality streamlines workflows, replac...

Installing CocoaPods on macOS Catalina (10.15) Using a User-Managed Ruby

System Ruby on macOS 10.15 frequently fails to build native gems required by CocoaPods (for example, ffi), leading to errors like: ERROR: Failed to build gem native extension checking for ffi.h... no...

Resolve PhpStorm "Interpreter is not specified or invalid" on WAMP (Windows)

Symptom PhpStorm displays: "Interpreter is not specified or invalid. Press ‘Fix’ to edit your project configuration." This occurs when the IDE cannot locate a valid PHP CLI executable or when the debu...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.