从0打1 用node express vite搭建一个博客系统系列(完结)

news2024/11/16 11:29:58

项目使用了Node后端、Express和Vite搭建的全栈博客系统系列,将Vue 2项目重构为Vue 3版本。该系统包含了以下主要模块和功能:

登录和注册功能:用户可以通过注册账号和登录来访问博客系统。

分类列表:展示不同分类的文章,包括技术栈、精品短文和热门文章。

更多功能:

数据分析:提供文章数据的统计和分析功能。
评论审查:对用户评论进行审查和管理。
回收站:将已删除的文章移至回收站,可以进行恢复或永久删除。
浏览历史:记录用户的浏览历史,方便查看之前浏览过的文章。
搜索:提供文章标题搜索功能,方便用户查找感兴趣的文章。
详情:点击文章标题可以查看文章的详细内容。
评论:允许用户在文章下发表评论。
点赞:用户可以给喜欢的文章点赞。
基本的增删改查功能:支持对文章进行基本的增加、删除、修改和查询操作。

通过这个博客系统,用户可以方便地管理和浏览文章,以及与其他用户进行互动和讨论。系统使用了最新的Vue 3版本,提供了更好的性能和开发体验。

项目开源地址:https://gitee.com/hailang123/kao2-vite-mysql
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

路由

import { createRouter, createWebHistory } from "vue-router";

const routes = [
  {
    path: "/login",
    name: "login",
    component: () => import("../views/login.vue"),
  },
  {
    path: "/register",
    name: "register",
    component: () => import("../views/register.vue"),
  },
  {
    path: "/",
    name: "index",
    component: () => import("../views/index.vue"),
    children: [
      {
        // 详情页,通过id来获取文章信息
        path: "/detail/:id",
        name: "detail",
        component: () => import("../views/detail.vue"),
      },

      {
        path: "/fabu",
        name: "fabu",
        component: () => import("../views/fabu.vue"),
      },
      {
        path: "/biaoqian",
        name: "biaoqian",
        component: () => import("../views/biaoqian.vue"),
      },
      {
        path: "/fenlei",
        name: "fenlei",
        component: () => import("../views/fenlei.vue"),
      },
      {
        path: "/shouye",
        name: "shouye",
        component: () => import("../views/shouye.vue"),
      },
      {
        path: "/lixiang",
        name: "lixiang",
        component: () => import("../views/lixiang.vue"),
      },
      {
        path: "/jishuzhan",
        name: "jishuzhan",
        component: () => import("../views/jishuzhan.vue"),
      },
      {
        path: "/fenxi",
        name: "fenxi",
        component: () => import("../views/fenxi.vue"),
      },
      {
        path: "/duanwen",
        name: "duanwen",
        component: () => import("../views/duanwen.vue"),
      },

      {
        path: "/labcloud",
        name: "labcloud",
        component: () => import("../views/labcloud.vue"),
      },
      {
        path: "/hotpage",
        name: "hotpage",
        component: () => import("../views/hotpage.vue"),
      },
      {
        path: "/newping",
        name: "newping",
        component: () => import("../views/newping.vue"),
      },
      {
        path: "/history",
        name: "history",
        component: () => import("../views/history.vue"),
      },
      {
        path: "/huishou",
        name: "huishou",
        component: () => import("../views/huishou.vue"),
      }
    ],
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

详情

<template>
  <div class="main">
    <div>
      <div class="title">{{ state.title }}</div>
      <div class="content">{{ state.content }}</div>
      <div class="author">作者:{{ state.author }}</div>
      <div><Dianzan :articleId=state.id></Dianzan></div>
      <div class="created_at">创建时间:{{ state.created_at }}</div>
    </div>
    <div>
      <a-button @click="bankFn" type="primary" :size="size" class="bank"
        >返回</a-button
      >
      <a-button @click="preFn" type="primary" :size="size" class="pre"
        >上一篇</a-button
      >
      <a-button
        @click="nestFn"
        :disabled="isLastPage"
        type="primary"
        :size="size"
        class="next"
        >下一篇</a-button
      >
    </div>
    <div>
      <Pinglun :articleId="route.params.id" />
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, computed } from "vue";
import { getArticleById, showPassageApi } from "../utils/api";
import { useRoute, useRouter } from "vue-router";
import Pinglun from "../components/pinglun.vue";
import Dianzan from "../components/dianzan.vue";

const state = ref({});
const route = useRoute();
const router = useRouter();

const result = async () => {
  const articleId = route.params.id;
  const res = await getArticleById({ id: articleId });
  state.value = res.data[0];
  console.log(state.value);

  // 获取当前浏览的时间
  const time = new Date().toLocaleString();

  // 将当前浏览详的时间添加到浏览详情数据中
  state.value.time = time;

  // 从localStorage获取已有的浏览记录
  let history = JSON.parse(localStorage.getItem("history")) || [];

  // 将当前浏览详情数据添加到浏览记录中
  history.push(state.value);

  // 将更新后的浏览记录保存回localStorage
  localStorage.setItem("history", JSON.stringify(history));
};

result();

// 返回到首页
const bankFn = () => {
  router.push("/shouye");
};

const preFn = async () => {
  const articleId = route.params.id;
  const res = await getArticleById({ id: articleId });
  console.log(res.data[0].id);
  if (res.data[0].id == 1) {
    alert("已经是第一篇了");
  } else {
    router.push(`/detail/${res.data[0].id - 1}`);
    state.value = res.data[0];
  }
};

const nestFn = async () => {
  const articleId = route.params.id;
  const res = await getArticleById({ id: articleId });
  console.log(res.code);
  if (res.data.length === 0) {
    //   阻止下一篇按钮的点击事件
    return;
    alert("已经是最后一篇了");
  } else {
    router.push(`/detail/${res.data[0].id + 1}`);
    state.value = res.data[0];
  }
};

const isLastPage = computed(() => {
  showPassageApi().then((res) => {
    // 计算一共有多少条数据
    const total = res.data.length;
    return state.value.id === total;
  });
});
</script>

<style lang="less" scoped>
.main {
  width: 100%;
  height: 100%;
  background-color: antiquewhite;

  .title {
    width: 100%;
    height: 50px;
    font-size: 20px;
    font-weight: bold;
    margin: 0 auto;
    text-align: center;
    padding: 15px;
  }
  .content {
    width: 100%;
    height: 100%;
    font-size: 15px;
    font-family: "微软雅黑";
    margin: 0 auto;
    padding: 15px;
    // 文字缩进2
    text-indent: 2em;
  }
  .author {
    width: 100%;
    height: 50px;
    font-size: 15px;
    font-family: "微软雅黑";
    margin: 0 auto;
    padding: 15px;
    text-align: right;
  }
  .created_at {
    width: 100%;
    height: 50px;
    font-size: 15px;
    font-family: "微软雅黑";
    margin: 0 auto;
    padding: 15px;
    text-align: right;
  }
}
</style>

首页

<template>
  <a-layout>
    <a-layout-header class="header">
      <a-menu
        v-model:selectedKeys="selectedKeys1"
        theme="dark"
        mode="horizontal"
        :style="{ lineHeight: '64px' }"
      >
        <a-menu-item key="1">
          <router-link to="/shouye"> 首页 </router-link>
        </a-menu-item>
        <a-menu-item key="2">
          <router-link to="/fenlei">文章分类 </router-link>
        </a-menu-item>
        <a-menu-item key="3">
          <router-link to="/biaoqian"> 标签 </router-link>
        </a-menu-item>
        <a-menu-item key="4">
          <router-link to="/fabu"> 发布 </router-link>
        </a-menu-item>
        <a-menu-item key="5" class="yidong">
          <router-link to="/login">登录</router-link>
        </a-menu-item>
        <a-menu-item key="6">
          <router-link to="/register">注册</router-link>
        </a-menu-item>
      </a-menu>
    </a-layout-header>
    <a-layout>
      <a-layout-sider width="200" style="background: #fff">
        <a-menu
          v-model:selectedKeys="selectedKeys2"
          v-model:openKeys="openKeys"
          mode="inline"
          :style="{ height: '100%', borderRight: 0 }"
        >
          <a-sub-menu key="sub1">
            <template #title>
              <span>
                <user-outlined />
                分类列表
              </span>
            </template>
            <a-menu-item key="1">
              <router-link to="/jishuzhan"> 技术栈 </router-link>
            </a-menu-item>
            <a-menu-item key="2">
              <router-link to="/duanwen"> 精品短文 </router-link>
            </a-menu-item>
            <a-menu-item key="3">
              <router-link to="/hotpage"> 热门文章 </router-link>
            </a-menu-item>
          </a-sub-menu>
          <a-sub-menu key="sub2">
            <template #title>
              <span>
                <laptop-outlined />
                更多
              </span>
            </template>
            <a-menu-item key="5">
              <router-link to="/fenxi"> 数据分析 </router-link>
            </a-menu-item>
            <a-menu-item key="6">
              <router-link to="/newping"> 评论审查 </router-link>
            </a-menu-item>
            <a-menu-item key="7">
              <router-link to="/history"> 浏览历史 </router-link>
            </a-menu-item>
            <a-menu-item key="8">
              <router-link to="/huishou"> 回收站 </router-link>
            </a-menu-item>
          </a-sub-menu>
          <a-sub-menu key="sub3">
            <template #title>
              <span>
                <notification-outlined />
                关于作者
              </span>
            </template>
            <a-menu-item key="9">个人简介</a-menu-item>
            <a-menu-item key="10">联系方式</a-menu-item>
            <a-menu-item key="11">友情链接</a-menu-item>
            <a-menu-item key="12">
              <router-link to="/lixiang"> 立项要求 </router-link>
            </a-menu-item>
          </a-sub-menu>
        </a-menu>
      </a-layout-sider>
      <a-layout style="padding: 0 24px 24px">
        <a-breadcrumb style="margin: 16px 0">
          <a-breadcrumb-item>
            <router-link to="/shouye">首页</router-link>
          </a-breadcrumb-item>
          <a-breadcrumb-item>
            {{ breadcrumbNameMap[$route.path] }}
          </a-breadcrumb-item>
        </a-breadcrumb>
        <a-layout-content
          :style="{
            background: '#fff',
            padding: '24px',
            margin: 0,
            minHeight: '280px',
          }"
        >
          <router-view />
        </a-layout-content>
      </a-layout>
    </a-layout>
    <Footer class="main" />
  </a-layout>
</template>
<script setup>
import { ref, computed } from "vue";
import Footer from "../components/footer.vue";

const selectedKeys1 = ref(["1"]);
const selectedKeys2 = ref(["2"]);
const collapsed = ref(false);
const openKeys = ref(["sub1"]);

// 定义面包屑
const breadcrumbNameMap = {
  "/shouye": "首页",
  "/fenlei": "文章分类",
  "/biaoqian": "标签",
  "/fabu": "发布",
  "/login": "登录",
  "/register": "注册",
  "/lixiang": "立项要求",
};

const currentBreadcrumb = computed(() => breadcrumbNameMap[$route.path]);
</script>
<style>
.yidong {
  margin-left: 900px;
}

#components-layout-demo-top-side-2 .logo {
  float: left;
  width: 120px;
  height: 31px;
  margin: 16px 24px 16px 0;
  background: rgba(255, 255, 255, 0.3);
}

.ant-row-rtl #components-layout-demo-top-side-2 .logo {
  float: right;
  margin: 16px 0 16px 24px;
}

.site-layout-background {
  background: #fff;
}

.main {
  margin: 0 auto;
  /* 固定底部 */
}
</style>

回收站模块

<template>
  <div class="main">
    <h1>回收站</h1>
    <div v-for="item in state" :key="item.id">
      <h4>标题:{{ item.title }}</h4>
      <p>内容:{{ item.content }}</p>
      <p>作者:{{ item.author }}</p>
      <p>创建时间:{{ item.created_at }}</p>
      <!-- 其他需要展示的内容 -->
      <a-button @click="bankFn(item.id)">恢复</a-button>
      <a-button>彻底删除</a-button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from "vue";
import { showPassageApi, recoverPassageApi } from "../utils/api";

const state = ref({});

// 获取showPassageApi
const data = async () => {
  const res = await showPassageApi();
  console.log(res);
  // 将过滤后的数据添加到state中
  res.data.forEach((item) => {
    // 过滤数据is_deleted为1的数据
    if (item.is_deleted === 1) {
      state.value[item.id] = item;
      console.log(item);
    }
  });
};

onMounted(() => {
  data();
});

// 恢复
const bankFn = (id) => {
  console.log(id);
  recoverPassageApi({ id: id }).then((res) => {
    console.log(res);
  });
// 删除当前行
  delete state.value[id];
};
</script>

<style lang="less" scoped>
.main {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  h3 {
    margin-top: 20px;
  }
  div {
    width: 100%;
    height: 220px;
    border: 1px solid #ccc;
    margin-top: 20px;
    padding: 10px;
    h4 {
      font-size: 20px;
    }
    p {
      font-size: 16px;
    }
  }
}
</style>

历史记录模块

<template>
  <div>
    <h2>浏览记录</h2>
    <ul v-for="page in history" :key="page">
      <li >{{ page.title }}</li>
      <li>{{ page.author }}</li>
      <li>{{ page.content }}</li>
      <li>{{ page.time }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      history: [],
    };
  },
  created() {
    // 读取本地存储的浏览记录
    const savedHistory = localStorage.getItem("history");
    if (savedHistory) {
      this.history = JSON.parse(savedHistory);
    }
  },
  methods: {
    addToHistory(page) {
      this.history.push(page);
      // 存储浏览记录到localStorage
      localStorage.setItem("history", JSON.stringify(this.history));
    },
  },
};
</script>

发布模块

<template>
  <a-form :form="form" @submit="handleSubmit">
    <a-form-item label="标题" name="title">
      <a-input v-model:value="form.title" />
    </a-form-item>
    <a-form-item label="内容" name="content">
      <a-textarea v-model:value="form.content" />
    </a-form-item>
    <a-form-item label="作者" name="author_id">
      <a-select v-model:value="form.author_name">
        <a-select-option
          v-for="author in authors"
          :key="author.id"
          :value="author.name"
        >
          {{ author.name }}
        </a-select-option>
      </a-select>
    </a-form-item>
    <a-form-item label="分类" name="category_id">
      <a-select v-model:value="form.category_id">
        <a-select-option
          v-for="category in categories"
          :key="category.id"
          :value="category.id"
        >
          {{ category.name }}
        </a-select-option>
      </a-select>
    </a-form-item>
    <a-form-item>
      <a-button type="primary" html-type="submit">发布</a-button>
    </a-form-item>
  </a-form>
</template>

<script setup>
import { ref } from "vue";
import { message } from "ant-design-vue";
import { addPassageApi, showCategoryApi } from "../utils/api.js";

// 获取浏览器当前登录用户的信息
const currentUser = localStorage.getItem("username");
const currentUserId = localStorage.getItem("id");

const authors = [{ id: currentUserId, name: currentUser }];

const categories = ref([]);

const formatDate = (date) => {
  const year = date.getFullYear();
  const month = (date.getMonth() + 1).toString().padStart(2, "0");
  const day = date.getDate().toString().padStart(2, "0");
  const hours = date.getHours().toString().padStart(2, "0");
  const minutes = date.getMinutes().toString().padStart(2, "0");
  const seconds = date.getSeconds().toString().padStart(2, "0");
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};

const form = ref({
  title: "",
  content: "",
  author_name: currentUser, // 使用当前用户名作为初始值
  category_id: null,
});

const handleSubmit = () => {
  const currentDate = new Date();

  form.value.created_at = formatDate(currentDate);
  form.value.updated_at = formatDate(currentDate);
  form.value.published_at = formatDate(currentDate);

  console.log(form.value);
  addPassageApi({
    title: form.value.title,
    content: form.value.content,
    author: form.value.author_name,
    category_id: form.value.category_id,
    created_at: form.value.created_at,
    updated_at: form.value.updated_at,
    published_at: form.value.published_at,
  }).then((res) => {
    console.log(res);
  });

  message.success("文章发布成功");
};

// 获取showCategoryApi()接口返回的数据
showCategoryApi().then((res) => {
  categories.value = res.data.map((item) => ({
    id: item.id,
    name: item.name,
  }));
  console.log(categories.value);
});

</script>

<style scoped>
.a-form-item {
  margin-bottom: 24px;
}
</style>

后端基本的接口

const express = require("express");
const router = express.Router();

const db = require("../utils/db");

// 文章接口

// 获取全部文章
router.post("/api/articleadd", (req, res) => {
  let sql = `select * from articles`;
  db.query(sql, (err, result) => {
    if (err) {
      res.json({
        status: 500,
        msg: "服务器内部错误",
      });
    } else {
      res.json({
        code: 200,
        msg: "获取成功",
        data: result,
      });
    }
  });
});

// 添加文章
router.post("/api/article", (req, res) => {
  const {
    title,
    content,
    author,
    category_id,
    created_at,
    updated_at,
    published_at,
  } = req.body;
  console.log(req.body);
  // 将文章信息保存到数据库
  const sql =
    "INSERT INTO articles (title, content, author, category_id, created_at, updated_at, published_at) VALUES (?, ?, ?, ?, ?, ?, ?)";
  const values = [
    title,
    content,
    author,
    category_id,
    created_at,
    updated_at,
    published_at,
  ];
  db.query(sql, values, (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "保存文章信息失败" });
    } else {
      res.json({ message: "保存文章信息成功" });
    }
  });
});

// export const getArticleById = (params) =>request.post("/api/articleById",params);
router.post("/api/articleById", (req, res) => {
  const { id } = req.body;
  console.log(req.body);
  const sql = "SELECT * FROM articles WHERE id = ?";
  db.query(sql, [id], (err, result) => {
    if (err) {
      console.error(err);
      res.send({ code: 500, msg: "获取文章信息失败" });
    } else {
      res.send({
        code: 200,
        msg: "获取成功",
        data: result,
      });
    }
  });
});

// export const getCategoryById = (params) =>request.post("/api/categoryById",params);

router.post("/api/categoryById", (req, res) => {
  const { id } = req.body;
  const sql = "SELECT * FROM categories WHERE id = ?";
  db.query(sql, [id], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "获取分类信息失败" });
    } else {
      res.json(result);
    }
  });
});

// export const searchApi = (params) =>reque st.post("/api/search",params);
router.post("/api/search", (req, res) => {
  const { searchValue } = req.body;
  let keyword = searchValue;
  const sql = `SELECT * FROM articles WHERE title LIKE '%${keyword}%'`;
  db.query(sql, (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "搜索失败" });
    } else {
      res.send({
        code: 200,
        msg: "获取成功",
        data: result,
      });
    }
  });
});

// export const getTagBytag = (params) =>request.post("/api/tagById",params);
router.post("/api/tagBytag", (req, res) => {
  const { tag } = req.body;
  const name = tag;
  const sql = "INSERT INTO tags (name) VALUES (?)"; // Modify the SQL statement to insert the name
  db.query(sql, [name], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "插入标签信息失败" }); // Update the error message for insertion failure
    } else {
      res.json(result);
    }
  });
});

// export const showTagApi = () => request.post("/api/tagadd");
router.post("/api/tagadd", (req, res) => {
  const sql = "SELECT * FROM tags";
  db.query(sql, (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "获取标签信息失败" });
    } else {
      res.json(result);
    }
  });
});

// export const addCommentApi = (params) => request.post("/api/comment", params);
router.post("/api/comment", (req, res) => {
  const { article_id, user_id, content, created_at, updated_at } = req.body;
  console.log(req.body);
  const sql =
    "INSERT INTO comments (article_id,user_id, content, created_at, updated_at) VALUES (?, ?, ?, DEFAULT, DEFAULT)";
  const values = [article_id, user_id, content, created_at, updated_at];
  db.query(sql, values, (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "保存评论信息失败" });
    } else {
      res.json({ message: "保存评论信息成功" });
    }
  });
});

// 删除文章
// export const deletePassageApi = (params) => request.post("/api/articledelete", params);
router.post("/api/articledelete", (req, res) => {
  const { id } = req.body;
  const sql = "UPDATE articles SET is_deleted = 1 WHERE id = ?";
  db.query(sql, [id], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "修改文章状态失败" });
    } else {
      res.send({
        code: 200,
        msg: "删除成功",
        data: result,
      });
    }
  });
});

// 恢复文章
// export const recoverPassageApi = (params) => request.post("/api/articlerecover", params);
router.post("/api/articlerecover", (req, res) => {
  const { id } = req.body;
  const sql = "UPDATE articles SET is_deleted = 0 WHERE id = ?";
  db.query(sql, [id], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "修改文章状态失败" });
    } else {
      res.send({
        code: 200,
        msg: "恢复成功",
        data: result,
      });
    }
  });
});

// export const getCommentApi = (params) => request.post("/api/getComment", params);
// 根据文章id获取评论
router.post("/api/getComment", (req, res) => {
  const { article_id } = req.body;
  const sql = "SELECT * FROM comments WHERE article_id = ?";
  db.query(sql, [article_id], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "获取评论信息失败" });
    } else {
      res.json(result);
    }
  });
});

// export const getAllCommentApi = () => request.post("/api/getAllComment");
// 获取全部评论
router.post("/api/getAllComment", (req, res) => {
  const sql = "SELECT * FROM comments";
  db.query(sql, (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "获取评论信息失败" });
    } else {
      res.send({
        code: 200,
        msg: "获取成功",
        data: result,
      });
    }
  });
});

// export const checkCommentApi = (params) => request.post("/api/checkComment", params);
// 审核评论
router.post("/api/checkComment", (req, res) => {
  const { id, approved } = req.body;
  console.log(req.body);
  const sql = "UPDATE comments SET approved = ? WHERE id = ?";
  db.query(sql, [approved, id], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "审核评论失败" });
    } else {
      res.json({ message: "审核评论成功" });
    }
  });
});

// export const addCategoryApi = (params) => request.post("/api/category", params);
router.post("/api/category", (req, res) => {
  const { name } = req.body;
  console.log(req.body);
  const sql = "INSERT INTO categories (name) VALUES (?)";
  db.query(sql, [name], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "添加分类失败" });
    } else {
      res.send({
        code: 200,
        msg: "添加成功",
        data: result,
      });
    }
  });
});

// export const showCategoryApi = () => request.post("/api/categoryadd");
router.post("/api/categoryadd", (req, res) => {
  const sql = "SELECT * FROM categories";
  db.query(sql, (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "获取分类信息失败" });
    } else {
      res.send({
        code: 200,
        msg: "获取成功",
        data: result,
      });
    }
  });
});

// export const deleteCategoryApi = (params) => request.post("/api/categorydelete", params);
router.post("/api/categorydelete", (req, res) => {
  const { id } = req.body;
  console.log(req.body);
  const sql = "DELETE FROM categories WHERE id = ?";
  db.query(sql, [id], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "删除分类失败" });
    } else {
      res.json({ message: "删除分类成功" });
    }
  });
});
 
// export const addLikeApi = (params) => request.post("/api/like", params);
router.post("/api/like", (req, res) => {
  const { article_id, likes_count } = req.body;
  console.log(req.body);
  const sql =
    "INSERT INTO articles_like (article_id, likes_count) VALUES (?, ?)";
  const values = [article_id, likes_count];
  db.query(sql, values, (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "保存点赞信息失败" });
    } else {
      // 获取点赞信息
      res.send({
        code: 200,
        msg: "保存成功",
        data: result,
      });
    }
  });
});

// export const getLikeApi = (params) => request.post("/api/getLike", params);
router.post("/api/getLike", (req, res) => {
  const { article_id } = req.body;
  const sql = "SELECT * FROM articles_like WHERE article_id = ?";
  db.query(sql, [article_id], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "获取点赞信息失败" });
    } else {
      res.send({
        code: 200,
        msg: "获取成功",
        data: result,
      });
    }
  });
});

// export const addCountApi = (params) => request.post("/api/count", params);
router.post("/api/count", (req, res) => {
  // 根据articles表中的article_id字段,插入到views_count字段
  const { id, views_count } = req.body;
  console.log(req.body);
  const sql = "UPDATE articles SET views_count = ? WHERE id = ?";
  db.query(sql, [views_count, id], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).json({ message: "保存浏览量信息失败" });
    } else {
      res.json({ message: "保存浏览量信息成功" });
    }
  });
});

module.exports = router;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/634878.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

编译LeGo-LOAM,并且采用速腾聚创激光雷达与之相连

目录 一、LeGo-LOAM部署二、速腾聚创激光雷达调试三、将速腾聚创激光雷达连到LeGo-LOAM四、解决LeGo-LOAM不保存pcd地图的问题 一、LeGo-LOAM部署 参考链接&#xff1a;实车部署采用速腾聚创RS16激光雷达的LeGo-LOAM LeGO-LOAM初探&#xff1a;原理&#xff0c;安装和测试 1.g…

C语言基础--整型int,长整型long,浮点型double float

本文讲解常见的C语言变量,并举出一些实例 从微软的C语言文档把所有的C语言可定义(就是能用的)截图展示: 还有好几页,不放了,看着都头疼 但是,往往用的最多的,也就是下面的(本篇只讲整数和浮点数) int 整数 整数的定义不用说了吧QAQ int a = 10; //定义一个…

深度学习应用篇-计算机视觉-视频分类[8]:时间偏移模块(TSM)、TimeSformer无卷积视频分类方法、注意力机制

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

本周大新闻|Vision Pro头显重磅发布;苹果收购AR厂商Mira

本周XR大新闻&#xff0c;上周Quest 3发布之后&#xff0c;本周苹果MR头显Vision Pro正式发布&#xff0c;也是本周AR/VR新闻的重头戏。 ​AR方面&#xff0c;苹果发布VST头显Vision Pro&#xff08;虽然本质是台VR&#xff0c;但以AR场景为核心&#xff09;以及visionOS&…

Qt动态调用(外部调用)SDK库(dll动态库)

Qt动态调用SDK库&#xff08;dll动态库&#xff09; ​​​​​​​ 之前的文章&#xff0c;介绍了Qt调用sdk库的方式&#xff0c;大家可以点击查看&#xff08;Q调用SDK库(dll动态库)&#xff09;之前的文章&#xff0c;里面介绍的调用方式就是静态的调用方式。如下图所示&am…

NLP:BIG-bench基准任务的简介、安装、使用方法之详细攻略

NLP&#xff1a;BIG-bench基准任务的简介、安装、使用方法之详细攻略 目录 BIG-bench基准任务的简介 1、BIG-bench基准任务的概述 2、BBL BIG-bench基准任务的安装 BIG-bench基准任务的使用方法 1、使用SeqIO加载BIG-bench json任务的快速启动 BIG-bench基准任务的简介 …

Jmeter常用参数化技巧总结!

说起接口测试&#xff0c;相信大家在工作中用的最多的还是Jmeter。 JMeter是一个100&#xff05;的纯Java桌面应用&#xff0c;由Apache组织的开放源代码项目&#xff0c;它是功能和性能测试的工具。具有高可扩展性、支持Web(HTTP/HTTPS)、SOAP、FTP、JAVA 等多种协议。 在做…

libVLC 调节图像(亮度、对比度、色调、饱和度、伽玛)

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 对于一个视频来说,色彩和画面效果的呈现非常重要。假如你的画面偏暗或偏亮,缺乏层次感,色彩不够丰富或不自然,则需要根据场景和氛围进行调整。 所涉及的重要参数有: 亮度: 是视频画面的明暗程度。调整…

静态通讯录

文章目录 前言&#x1f31f;一、明确通讯录信息/步骤&#x1f30f;1.1.通讯录信息&#x1f30f;1.2.通讯录多文件写法的好处&#x1f30f;1.3.通讯录多文件写法 &#x1f31f;二、通讯录的实现&#x1f30f;2.1.通讯录的菜单部分&#x1f30f;2.2.通讯录的框架部分&#x1f30f…

centos卸载mysql5.7安装mysql8.0

一、参考 CentOS7安装MySQL8的超级详细教程(无坑!)_Mysql_脚本之家 云服务器Centos7.9卸载 MySQL5.7.x 或 MySQL8.x 教程_centos 卸载mysql_大白有点菜的博客-CSDN博客 二、centos卸载mysql5.7 1、查看MySQL的依赖安装组件 rpm -qa|grep -i mysql 2、 依次移除&#xff08…

Canvas实现缩放+涂鸦改进

几个月以前&#xff0c;有人问了我一个canvass怎么实现缩放和涂鸦的问题&#xff0c;我基于当时的想法写了一篇博客&#xff0c;但是后来发现当时做的不完善&#xff0c;所以实现上其实还是有一些其他问题的。但是因为前段时间太忙了&#xff0c;也就一直没有机会去改进它。现在…

超详细:实现 Swift 与 汇编(Asm)代码混编并在真机或模拟器上运行

功能需求 虽然现在  开发的绝对主角是 Swift 语言&#xff0c;不过我们也希望有时 Swift 能够调用小段汇编代码以完成特殊功能。 在本篇博文中&#xff0c;您将学到如下内容&#xff1a; Swift 与 汇编语言混编的基本原理&#xff1b;如何在模拟器中使用 Swift x64 汇编指…

小黑带领阿黄中老黑一起跑步完成了小怪兽,晚上一起吃烤肠西瓜,买了帐篷准备一起露营的leetcode之旅:438. 找到字符串中所有字母异位词

小黑代码 class Solution:def findAnagrams(self, s: str, p: str) -> List[int]:# 串p长度n_p len(p)# 串s长度n_s len(s)# 计数字典flags collections.Counter(p)# 统计字典map_ dict((k, 0) for k in p)# 匹配到的字符个数count 0# 头尾指针left right 0# 目标数…

Python给一个exe执行文件注册持续性的快捷键(热键)的代码实例

本篇文章主要讲解通过python给一个exe文件绑定一个快捷键、并取消快捷键(热键)的实操方法。 日期:2023年6月11日 作者:任聪聪 实现按下快捷键即可启动软件的效果说明 启动软件注册热键呼出其他软件或本体的效果说明: 演示材料说明:在download文件目录下存放一个可执行的…

数据结构与算法之美 | 栈

栈结构&#xff1a;后进者先出&#xff0c;先进者后出 栈是一种“操作受限”的线性表 当某个数据集合只涉及在一端插入和删除数据&#xff0c;并且满足后进先出、先进后出的特性&#xff0c;这时我们就应该首选“栈”这种数据结构 栈的实现 使用数组实现&#xff1a;顺序栈…

【数据结构】二叉树(一)

目录 一、树的概念及结构 1、树的概念 2、树的相关概念 3、树的表示 二、二叉树概念及结构 1、二叉树的概念 2、特殊二叉树 3、二叉树的性质 4、二叉树的存储结构 4.1 顺序存储结构 4.2 链式存储结构 三、二叉树顺序结构及实现 1、二叉树的顺序结构 2、堆的概念及结构 3、堆…

OMG--RTPS(Real Time Publish Subscribe Protocol)

OMG--RTPS&#xff08;Real Time Publish Subscribe Protocol&#xff09; 1 概述2 内容缩写DDS 有线协议的要求RTPS 有线协议The RTPS Platform Independent Model (PIM)The Structure ModuleThe Messages ModuleThe Behavior ModuleThe Discovery Module The RTPS Platform S…

Xuperchain多节点网络搭建+加节点+测试

环境准备 创建网络部署环境 # 在xuperchain目录执行 make testnet 种子节点 # 查看node1节点连接地址netURL cd node1 ./bin/xchain-cli netURL preview # 得到如下结果,实际使用时,需要将ip配置节点的真实ip,port配置成 /ip4/{{ip}}/tcp/{{port}}/p2p/Qmf2HeHe4sspGkfR…

深度学习应用篇-计算机视觉-OCR光学字符识别[7]:OCR综述、常用CRNN识别方法、DBNet、CTPN检测方法等、评估指标、应用场景

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

上课补充的知识

题目 char类型的默认值是\u0000 数组的创建方式 数组的遍历 遍历:从头到尾,依次访问数组每一个位置,获取每一个位置的元素.形式如下: 我们通过数组的下标操作数组,所以for循环变量操作的也是数组下标 开始:开始下标0 结束:结束下标length-1 如何变化: 语法&#xff1a; for…