当前位置: 首页 > news >正文

Express 加 sqlite3 写一个简单博客

例图:

搭建 命令: 

前提已装好node.js

开始创建项目结构

npm init -y

package.json:{"name": "ex01","version": "1.0.0","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"keywords": [],"author": "","license": "ISC","description": ""
}

安装必要的依赖

npm install express sqlite3 ejs express-session body-parser

目录:

代码:

 app.js

const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const path = require('path');
const db = require('./database');const app = express();// 配置中间件
app.set('view engine', 'ejs');
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({secret: 'blog_secret_key',resave: false,saveUninitialized: true
}));// 首页路由
app.get('/', async (req, res) => {try {const category_id = req.query.category;const search = req.query.search;let posts;let categories = await db.all('SELECT * FROM categories');if (search) {// 搜索标题和内容posts = await db.all(`SELECT posts.*, categories.name as category_name FROM posts LEFT JOIN categories ON posts.category_id = categories.id WHERE title LIKE ? OR content LIKE ?ORDER BY created_at DESC`, [`%${search}%`, `%${search}%`]);} else if (category_id) {posts = await db.all(`SELECT posts.*, categories.name as category_name FROM posts LEFT JOIN categories ON posts.category_id = categories.id WHERE category_id = ? ORDER BY created_at DESC`, [category_id]);} else {posts = await db.all(`SELECT posts.*, categories.name as category_name FROM posts LEFT JOIN categories ON posts.category_id = categories.id ORDER BY created_at DESC`);}res.render('index', { posts, categories, current_category: category_id,search_query: search || ''});} catch (err) {res.status(500).send('数据库错误');}
});// 创建博文页面
app.get('/post/new', async (req, res) => {try {const categories = await db.all('SELECT * FROM categories');res.render('new', { categories });} catch (err) {res.status(500).send('获取分类失败');}
});// 提交新博文
app.post('/post/new', async (req, res) => {const { title, content, category_id } = req.body;try {await db.run('INSERT INTO posts (title, content, category_id, created_at) VALUES (?, ?, ?, ?)',[title, content, category_id, new Date().toISOString()]);res.redirect('/');} catch (err) {res.status(500).send('创建博文失败');}
});// 查看单篇博文
app.get('/post/:id', async (req, res) => {try {const post = await db.get(`SELECT posts.*, categories.name as category_name FROM posts LEFT JOIN categories ON posts.category_id = categories.id WHERE posts.id = ?`, [req.params.id]);if (post) {res.render('post', { post });} else {res.status(404).send('博文不存在');}} catch (err) {res.status(500).send('数据库错误');}
});// 编辑博文页面
app.get('/post/:id/edit', async (req, res) => {try {const post = await db.get('SELECT * FROM posts WHERE id = ?', [req.params.id]);const categories = await db.all('SELECT * FROM categories');if (post) {res.render('edit', { post, categories });} else {res.status(404).send('博文不存在');}} catch (err) {res.status(500).send('数据库错误');}
});// 更新博文
app.post('/post/:id/edit', async (req, res) => {const { title, content, category_id } = req.body;try {await db.run('UPDATE posts SET title = ?, content = ?, category_id = ? WHERE id = ?',[title, content, category_id, req.params.id]);res.redirect(`/post/${req.params.id}`);} catch (err) {res.status(500).send('更新博文失败');}
});// 删除博文
app.post('/post/:id/delete', async (req, res) => {try {await db.run('DELETE FROM posts WHERE id = ?', [req.params.id]);res.redirect('/');} catch (err) {res.status(500).send('删除博文失败');}
});const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {console.log(`服务器运行在 http://localhost:${PORT}`);
}); 

database.js

const sqlite3 = require('sqlite3').verbose();
const path = require('path');// 创建数据库连接
const db = new sqlite3.Database(path.join(__dirname, 'blog.db'), (err) => {if (err) {console.error('数据库连接失败:', err);} else {console.log('成功连接到数据库');initDatabase().catch(err => {console.error('数据库初始化失败:', err);});}
});// 初始化数据库表
async function initDatabase() {try {// 检查表是否存在const tablesExist = await get(`SELECT name FROM sqlite_master WHERE type='table' AND (name='posts' OR name='categories')`);if (!tablesExist) {console.log('首次运行,创建数据库表...');// 创建分类表await run(`CREATE TABLE IF NOT EXISTS categories (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT NOT NULL UNIQUE)`);console.log('分类表创建成功');// 创建文章表await run(`CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT,title TEXT NOT NULL,content TEXT NOT NULL,category_id INTEGER,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,FOREIGN KEY (category_id) REFERENCES categories(id))`);console.log('文章表创建成功');// 插入默认分类await run(`INSERT INTO categories (name) VALUES ('技术'),('生活'),('随笔')`);console.log('默认分类创建成功');} else {console.log('数据库表已存在,跳过初始化');}} catch (err) {console.error('数据库初始化错误:', err);throw err;}
}// Promise 包装数据库操作
function run(sql, params = []) {return new Promise((resolve, reject) => {db.run(sql, params, function(err) {if (err) {console.error('SQL执行错误:', err);reject(err);} else {resolve(this);}});});
}function get(sql, params = []) {return new Promise((resolve, reject) => {db.get(sql, params, (err, result) => {if (err) {console.error('SQL执行错误:', err);reject(err);} else {resolve(result);}});});
}function all(sql, params = []) {return new Promise((resolve, reject) => {db.all(sql, params, (err, rows) => {if (err) {console.error('SQL执行错误:', err);reject(err);} else {resolve(rows);}});});
}// 关闭数据库连接
process.on('SIGINT', () => {db.close((err) => {if (err) {console.error('关闭数据库时出错:', err);} else {console.log('数据库连接已关闭');}process.exit(0);});
});module.exports = {run,get,all
}; 

 views\index.ejs

<!DOCTYPE html>
<html>
<head><title>我的博客</title><meta charset="UTF-8"><style>body { font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;}.post {margin-bottom: 20px;padding: 15px;border: 1px solid #ddd;border-radius: 5px;}.post h2 {margin-top: 0;}.post-date {color: #666;font-size: 0.9em;}.new-post-btn {display: inline-block;padding: 10px 20px;background-color: #007bff;color: white;text-decoration: none;border-radius: 5px;margin-bottom: 20px;}.categories {margin: 20px 0;padding: 10px 0;border-bottom: 1px solid #eee;}.category-link {display: inline-block;padding: 5px 10px;margin-right: 10px;text-decoration: none;color: #666;border-radius: 3px;}.category-link.active {background-color: #007bff;color: white;}.post-category {display: inline-block;padding: 3px 8px;background-color: #e9ecef;border-radius: 3px;font-size: 0.9em;margin-right: 10px;}.search-box {margin: 20px 0;display: flex;gap: 10px;}.search-input {flex: 1;padding: 8px;border: 1px solid #ddd;border-radius: 4px;font-size: 1em;}.search-btn {padding: 8px 20px;background-color: #007bff;color: white;border: none;border-radius: 4px;cursor: pointer;}.search-btn:hover {background-color: #0056b3;}.search-results {margin-bottom: 20px;padding: 10px;background-color: #f8f9fa;border-radius: 4px;}.search-results-hidden {display: none;}</style>
</head>
<body><h1>博客文章列表</h1><a href="/post/new" class="new-post-btn">写新文章</a><form class="search-box" action="/" method="GET"><input type="text" name="search" class="search-input" placeholder="搜索文章标题或内容..." value="<%= search_query %>"><button type="submit" class="search-btn">搜索</button></form><div class="search-results <%= !search_query ? 'search-results-hidden' : '' %>">搜索结果: "<%= search_query || '' %>" - 找到 <%= posts ? posts.length : 0 %> 篇文章</div><div class="categories"><a href="/" class="category-link <%= !current_category ? 'active' : '' %>">全部</a><% categories.forEach(function(category) { %><a href="/?category=<%= category.id %>" class="category-link <%= current_category == category.id ? 'active' : '' %>"><%= category.name %></a><% }); %></div><% if (posts && posts.length > 0) { %><% posts.forEach(function(post) { %><div class="post"><h2><a href="/post/<%= post.id %>"><%= post.title %></a></h2><div class="post-meta"><span class="post-category"><%= post.category_name || '未分类' %></span><span class="post-date">发布时间: <%= new Date(post.created_at).toLocaleString() %></span></div><p><%= post.content.substring(0, 200) %>...</p></div><% }); %><% } else { %><p>还没有任何博客文章。</p><% } %>
</body>
</html> 

views\post.ejs 

<!DOCTYPE html>
<html>
<head><title><%= post.title %> - 我的博客</title><meta charset="UTF-8"><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;}.post-title {margin-bottom: 10px;}.post-meta {color: #666;margin-bottom: 20px;}.post-content {line-height: 1.6;white-space: pre-wrap;}.back-link {display: inline-block;margin-bottom: 20px;color: #007bff;text-decoration: none;}.post-category {display: inline-block;padding: 3px 8px;background-color: #e9ecef;border-radius: 3px;font-size: 0.9em;margin-right: 10px;}.action-buttons {margin: 20px 0;display: flex;gap: 10px;}.edit-btn {padding: 5px 15px;background-color: #28a745;color: white;text-decoration: none;border-radius: 3px;font-size: 0.9em;}.delete-btn {padding: 5px 15px;background-color: #dc3545;color: white;border: none;border-radius: 3px;cursor: pointer;font-size: 0.9em;}.delete-btn:hover {background-color: #c82333;}</style>
</head>
<body><a href="/" class="back-link">← 返回首页</a><article><h1 class="post-title"><%= post.title %></h1><div class="post-meta"><span class="post-category"><%= post.category_name || '未分类' %></span><span class="post-date">发布时间: <%= new Date(post.created_at).toLocaleString() %></span></div><div class="action-buttons"><a href="/post/<%= post.id %>/edit" class="edit-btn">编辑文章</a><form action="/post/<%= post.id %>/delete" method="POST" style="display: inline;" onsubmit="return confirm('确定要删除这篇文章吗?');"><button type="submit" class="delete-btn">删除文章</button></form></div><div class="post-content"><%= post.content %></div></article>
</body>
</html> 

 views\new.ejs

<!DOCTYPE html>
<html>
<head><title>写新文章 - 我的博客</title><meta charset="UTF-8"><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;}form {display: flex;flex-direction: column;}input, textarea, select {margin: 10px 0;padding: 8px;border: 1px solid #ddd;border-radius: 4px;}textarea {height: 300px;}button {padding: 10px 20px;background-color: #007bff;color: white;border: none;border-radius: 5px;cursor: pointer;}button:hover {background-color: #0056b3;}.back-link {display: inline-block;margin-bottom: 20px;color: #007bff;text-decoration: none;}label {margin-top: 10px;color: #666;}</style>
</head>
<body><a href="/" class="back-link">← 返回首页</a><h1>写新文章</h1><form action="/post/new" method="POST"><label for="title">文章标题</label><input type="text" id="title" name="title" placeholder="文章标题" required><label for="category">选择分类</label><select id="category" name="category_id" required><option value="">请选择分类</option><% categories.forEach(function(category) { %><option value="<%= category.id %>"><%= category.name %></option><% }); %></select><label for="content">文章内容</label><textarea id="content" name="content" placeholder="文章内容" required></textarea><button type="submit">发布文章</button></form>
</body>
</html> 

 views\edit.ejs

<!DOCTYPE html>
<html>
<head><title>编辑文章 - 我的博客</title><meta charset="UTF-8"><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;}form {display: flex;flex-direction: column;}input, textarea, select {margin: 10px 0;padding: 8px;border: 1px solid #ddd;border-radius: 4px;}textarea {height: 300px;}button {padding: 10px 20px;background-color: #007bff;color: white;border: none;border-radius: 5px;cursor: pointer;}button:hover {background-color: #0056b3;}.back-link {display: inline-block;margin-bottom: 20px;color: #007bff;text-decoration: none;}label {margin-top: 10px;color: #666;}</style>
</head>
<body><a href="/post/<%= post.id %>" class="back-link">← 返回文章</a><h1>编辑文章</h1><form action="/post/<%= post.id %>/edit" method="POST"><label for="title">文章标题</label><input type="text" id="title" name="title" value="<%= post.title %>" required><label for="category">选择分类</label><select id="category" name="category_id" required><option value="">请选择分类</option><% categories.forEach(function(category) { %><option value="<%= category.id %>" <%= post.category_id == category.id ? 'selected' : '' %>><%= category.name %></option><% }); %></select><label for="content">文章内容</label><textarea id="content" name="content" required><%= post.content %></textarea><button type="submit">更新文章</button></form>
</body>
</html> 

运行:

node app.js

服务器运行在 http://localhost:3000

相关文章:

Express 加 sqlite3 写一个简单博客

例图&#xff1a; 搭建 命令&#xff1a; 前提已装好node.js 开始创建项目结构 npm init -y package.json:{"name": "ex01","version": "1.0.0","main": "index.js","scripts": {"test": &q…...

正则表达式进阶学习(一):环视、捕获分组与后向引用

一、环视&#xff08;零宽断言&#xff09; 理论部分 环视&#xff08;零宽断言&#xff09;是一种用于匹配位置而非字符的正则表达式技术。它的核心特点是&#xff1a;不消耗字符&#xff0c;只检查某个位置前后是否符合特定的条件。可以理解为&#xff0c;环视是在匹配前“…...

《Vue3 七》插槽 Slot

插槽可以让组件的使用者来决定组件中的某一块区域到底存放什么元素和内容。 使用插槽&#xff1a; 插槽的使用过程其实就是抽取共性、预留不同。将共同的元素、内容依然留在组件内进行封装&#xff1b;将不同的元素使用 slot 作为占位&#xff0c;让外部决定到底显示什么样的…...

【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 一、线性表的基本概念 二、初始化线性表 三、销毁线性表 四、判定是否为空表 五、求线性表的长度 六、输出线性表 七、求线性表中某个数据元素值 八、按元素值查找 九、插入数据元素 十、删除数据元素 测试说明 通关代码 测…...

Linux C/C++编程-获得套接字地址、主机名称和主机信息

【图书推荐】《Linux C与C一线开发实践&#xff08;第2版&#xff09;》_linux c与c一线开发实践pdf-CSDN博客《Linux C与C一线开发实践&#xff08;第2版&#xff09;&#xff08;Linux技术丛书&#xff09;》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com…...

USB kbtab linux 驱动代码

#include <linux/kernel.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/usb/input.h> #include <asm/unaligned.h> /* Pressure-threshold modules param code from */MODULE_AUTHOR(“xxx”); MODULE_DESCRIPTION(“…...

力扣 跳跃游戏

每次更新目标位置时&#xff0c;实际上是在做一个局部的最优选择&#xff0c;选择跳跃能够到达当前目标位置的最远位置。因为每次更新目标位置时&#xff0c;都是基于当前能跳跃到的最远位置&#xff0c;因此最终的结果是全局最优的。 题目 从前往后遍历&#xff0c;更新可以到…...

使用npm 插件[mmdc]将.mmd时序图转换为图片

使用npm 插件[mmdc]将.mmd时序图转换为图片 1. 安装 mmdc2. 转换为图片 可以使用 mmdc &#xff08;Mermaid CLI&#xff09;这个工具来将 .mmd 时序图&#xff08;Mermaid语法描述的时序图&#xff09;转换为图片&#xff0c;以下是使用步骤&#xff1a; 1. 安装 mmdc 确保…...

ffmpeg 常用命令

更详细请参考ffmpeg手册&#xff0c;下载ffmpegrelease版后在doc中就有&#xff0c;主页面。video filter 参考ffmpeg-filters.html -version -formats -demuxers -protocols -muxers -filters -devices —pix_fmts -codecs -sample_fmts -decoders -layouts -encoders -colors…...

从入门到实战:C 语言 strlen 函数通关指南

文章目录 一、strlen函数简介1. 函数构成2. 参数说明3. 使用示例 二、模拟实现strlen函数&#xff08;从新手角度逐步升级改进&#xff09;1. 基础版本&#xff08;利用循环计数&#xff09;2. 改进版本&#xff08;利用指针相减&#xff09;3. 递归版本&#xff08;利用递归思…...

npm install --global windows-build-tools --save 失败

注意以下点 为啥下载windows-build-tools&#xff0c;是因为node-sass4.14.1 一直下载不成功&#xff0c;提示python2 没有安装&#xff0c;最终要安装这个&#xff0c;但是安装这个又失败&#xff0c;主要有以下几个要注意的 1、node 版本 14.21.3 不能太高 2、管理员运行 …...

十种基础排序算法(C语言实现,带源码)(有具体排序例子,适合学习理解)

学习了十种常见的排序方法&#xff0c;此文章针对所学的排序方法进行整理&#xff08;通过C语言完成排序&#xff09;。 参考内容&#xff1a; https://blog.csdn.net/mwj327720862/article/details/80498455 https://www.runoob.com/w3cnote/ten-sorting-algorithm.html 1. 冒…...

基于fMRI数据计算脑脊液(CSF)与全脑BOLD信号的时间耦合分析

一、前言 笔者之前的文章《基于Dpabi和spm12的脑脊液(csf)分割和提取笔记》,介绍了如何从普通的fMRI数据中提取CSF信号。首先是基础的预处理,包括时间层校正、头动校正,再加上0.01-0.1Hz的带通滤波。接着用SPM12分割出CSF区域,设置一个比较严格的0.9阈值,确保提取的真是…...

实现websocket心跳检测,断线重连机制

WebSocket基础 WebSocket概念 WebSocket是一种革命性的 全双工通信协议 &#xff0c;构建在TCP之上&#xff0c;旨在简化客户端与服务器之间的数据交换过程。通过单次握手建立持久连接&#xff0c;WebSocket实现了真正的双向实时通信&#xff0c;显著提高了交互效率。这一特性…...

ComfyUI节点安装笔记

AI高速发展&#xff0c;版本更新相当快&#xff08;11月25日才安装的版本v.0.3.4&#xff0c;27日版本就已经更新到v.0.3.5了&#xff09;&#xff0c;在遇到问题&#xff0c;找到问题原因所在的过程中&#xff0c;ComfyUI版本、python版本、节点对环境版本的依赖&#xff0c;本…...

深度学习,训练集准确率高,但验证集准确率一直不上升,很低的问题

在训练过程中&#xff0c;训练集的准确率稳步上升&#xff0c;但是验证集的准确率一直在40%左右徘徊&#xff0c;从网上搜索可能的原因有&#xff1a; 1、学习率太小&#xff0c;陷入局部最优。 2、数据量太小&#xff08;4000多条数据&#xff0c;应该还可以吧&#xff09; …...

【C语言程序设计——选择结构程序设计】求输入的日期是该年的第几天(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 1、switch 结构基本语法 2、示例代码及解释 3、使用注意事项 4、判断闰年的条件 编程要求 测试说明 通关代码 测试结果 任务描述 本关任务&#xff1a;编写程序实现&#xff1a;从键盘上输入一个年月日&#xff08;以空格或回车…...

Lumos学习王佩丰Excel二十四讲系列完结

“Lumos学习王佩丰Excel二十四讲系列”是一套完整的Excel教程&#xff0c;涵盖了从基础到高级的各种知识和技能。是我亲自一个个码出来的教程哇&#xff01;&#xff01;&#xff01; 一、课程概览 该教程共分为24讲&#xff0c;每一讲都围绕Excel的一个核心主题进行深入讲解…...

前后端规约

文章目录 引言I 【强制】前后端交互的 API请求内容响应体响应码II 【推荐】MVC响应体III【参考】IV 其他引言 服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL 统一代理模块生成,否则会因线上采用 HTTPS 协议而导致浏览器提示“不安全”,并且还会带来 URL 维护…...

【数据可视化】数据可视化看板需求梳理模板(含示例)

一、模板 设计一个数据可视化看板需要从多个方面梳理需求&#xff0c;以确保看板能够有效地传达信息并满足用户的需求。以下是一些关键方面&#xff1a; 1.目标和受众 ● 明确目标&#xff1a;确定看板的主要目的&#xff0c;例如监控业务指标、分析市场趋势、展示项目进度等…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态

前言 在人工智能技术飞速发展的今天&#xff0c;深度学习与大模型技术已成为推动行业变革的核心驱动力&#xff0c;而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心&#xff0c;系统性地呈现了两部深度技术著作的精华&#xff1a;…...

32单片机——基本定时器

STM32F103有众多的定时器&#xff0c;其中包括2个基本定时器&#xff08;TIM6和TIM7&#xff09;、4个通用定时器&#xff08;TIM2~TIM5&#xff09;、2个高级控制定时器&#xff08;TIM1和TIM8&#xff09;&#xff0c;这些定时器彼此完全独立&#xff0c;不共享任何资源 1、定…...

基于stm32F10x 系列微控制器的智能电子琴(附完整项目源码、详细接线及讲解视频)

注&#xff1a;文章末尾网盘链接中自取成品使用演示视频、项目源码、项目文档 所用硬件&#xff1a;STM32F103C8T6、无源蜂鸣器、44矩阵键盘、flash存储模块、OLED显示屏、RGB三色灯、面包板、杜邦线、usb转ttl串口 stm32f103c8t6 面包板 …...