프로그래밍 언어/NODE JS

스케줄링 구현하기(2)

· 코딩마이데이

node-schedule 패키지의 단점은 스케줄링이 노드 기반으로 작동하므로 노드가 종료되면 스케줄 예약도 같이 종료된다는 점입니다. 노드를 계속 켜두면 되지만, 서버가 어떤 에러로 인해 종료될지 예측하기는 매우 어렵습니다. 따라서 이를 보완하기 위한 보안하기 위한 방법이 필요합니다. 서버가 시작될 때 경매 시작 후 24시간이 지났지만 낙찰자가 없는 경매를 찾아서 낙찰자를 지정하는 코드를 추가해보겠습니다.

 

checkAuction.js

const { Op } = require("sequelize");

const { Good, Auction, User, sequelize } = require("./models");

module.exports = async () => {
  try {
    const yesterday = new Date();
    yesterday.setDate(yesterday.getDate() - 1); // 어제 시간
    const targets = await Good.findAll({
      where: {
        SoldId: null,
        createdAt: { [Op.lte]: yesterday },
      },
    });
    targets.forEach(async (target) => {
      const success = await Auction.findOne({
        where: { GoodId: target.id },
        order: [["bid", "DESC"]],
      });
      await Good.update(
        { SoldId: success.UserId },
        { where: { id: target.id } },
      );
    });
    await User.update(
      {
        money: sequelize.literal(`money - ${success.bid}`),
      },
      {
        where: { id: success.UserId },
      },
    );
  } catch (error) {
    console.error(error);
  }
};

 

낙찰자가 없으면서 생성된지 24시간이 지난 경매를 찾아 낙찰자를 정합니다.

 

app.js

const express = require("express");
const path = require("path");
const morgan = require("morgan");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const passport = require("passport");
const nunjucks = require("nunjucks");
const dotenv = require("dotenv");

dotenv.config();
const indexRouter = require("./routes/index");
const authRouter = require("./routes/auth");
const { sequelize } = require("./models");
const passportConfig = require("./passport");
const sse = require("./see");
const webSocket = require("./socket");
const checkAuction = require("./checkAuction");

const app = express();
passportConfig();
checkAuction();
app.set("port", process.env.PORT || 8010);
app.set("view engine", "html");
nunjucks.configure("views", {
  express: app,
  watch: true,
});
sequelize
  .sync({ force: false })
  .then(() => {
    console.log("데이터베이스 연결 성공");
  })
  .catch((err) => {
    console.error(err);
  });

const sessionMiddleware = session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
});

app.use(morgan("dev"));
app.use(express.static(path.join(__dirname, "public")));
app.use("/img", express.static(path.join(__dirname, "uploads")));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(sessionMiddleware);
app.use(passport.initialize());
app.use(passport.session());

app.use("/", indexRouter);
app.use("/auth", authRouter);

app.use((req, res, next) => {
  const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== "production" ? err : {};
  res.status(err.status || 500);
  res.render("error");
});

const server = app.listen(app.get("port"), () => {
  console.log(app.get("port"), "번 포트에서 대기중");
});

webSocket(server, app);
sse(server);

 

checkAuction을 서버에 연결합니다. 서버를 재시작하면 앞으로 서버를 시작할 때마다 낙찰자를 지정하는 작업을 수행합니다. checkAuction의 코드는 app.js에 직접 작성해도 되지만 코드가 길어지므로 분리했습니다.

하루가 지나 경매가 마무리되면 node-schedule 모듈이 예정된 스케줄에 따라 낙찰자를 지정합니다. 단, 서버가 계속 커져 있어야 합니다. 서버가 중간에 꺼졌다면 다시 켤 때 checkAuction.js 코드에 따라 낙찰자를 선정하게 됩니다.