|
项目主体搭建
- 前端:vue3、element-plus、ts、axios、vue-router、pinia
- 后端:nodejs、koa、koa-router、koa-body、jsonwebtoken
- 部署:nginx、pm2、xshell、腾讯云自带宝塔面板
- 数据库:mysql、redis
- 开发软件:vs code、Another Redis Desktop Manager、Navicat Premium 15
后端主要使用的依赖包
- dotenv: 将环境变量中的变量从 .env 文件加载到 process.env 中
- jsonwebtoken: 颁发token,不会缓存到mysql和redis中
- koa: 快速搭建后台服务的框架
- koa-body: 解析前端传来的参数,并将参数挂到ctx.request.body上
- koa-router: 路由中间件,处理不同url路径的请求
- koa2-cors: 处理跨域请求的中间件
- mysql2: 在nodejs中连接和操作mysql数据库,mysql也可以,不过要自己封装连接数据库
- redis: 在nodejs中操作redis的库,通常用作持久化token、点赞等功能
- sequelize: 基于promise的orm(对象关系映射)库,不用写sql语句,更方便的操作数据库
- nodemon: 自动重启服务
- sequelize-automete: 自动化为sequelize生成模型
文件划分
常量文件配置
- 创建.env.development和config文件夹,配置如下
- # 数据库ip地址
- APP_HOST = 1.15.42.9
- # 服务监听端口
- APP_PORT = 40001
- # 数据库名
- APP_DATA_BASE = test
- # 用户名
- APP_USERNAME = test
- # 密码
- APP_PASSWORD = 123456
- # redis地址
- APP_REDIS_HOST = 1.15.42.9
- # redis端口
- APP_REDIS_PORT = 6379
- # redis密码
- APP_REDIS_PASSWORD = 123456
- # redis仓库
- APP_REDIS_DB = 15
- # websocket后缀
- APP_BASE_PATH = /
- # token标识
- APP_JWT_SECRET = LOVE-TOKEN
- # 保存文件的绝对路径
- APP_FILE_PATH = ""
- # 网络url地址
- APP_NETWORK_PATH = blob:http://192.168.10.20:40001/
复制代码 然后在config文件中将.env的配置暴露出去- const dotenv = require("dotenv");
- const path = process.env.NODE_ENV
- ? ".env.development"
- : ".env.production.local";
- dotenv.config({ path });
- module.exports = process.env;
复制代码 2.在最外层创建app.js文件- const Koa = require("koa");
- const http = require("http");
- const cors = require("koa2-cors");
- const WebSocket = require("ws");
- const { koaBody } = require("koa-body");
- const { APP_PORT, APP_BASE_PATH } = require("./src/config/index");
- const router = require("./src/router/index");
- const seq = require("./src/mysql/sequelize");
- const PersonModel = require("./src/models/person");
- const Mperson = PersonModel(seq);
- // 创建一个Koa对象
- const app = new Koa();
- const server = http.createServer(app.callback());
- // 同时需要在nginx配置/ws
- const wss = new WebSocket.Server({ server, path: APP_BASE_PATH }); // 同一端口监听不同的服务
- // 使用了代理
- app.proxy = true;
- // 处理跨域
- app.use(cors());
- // 解析请求体(也可以使用koa-body)
- app.use(
- koaBody({
- multipart: true,
- // textLimit: "1mb", // 限制text body的大小,默认56kb
- formLimit: "10mb", // 限制表单请求体的大小,默认56kb,前端报错413
- // encoding: "gzip", // 前端报415
- formidable: {
- // uploadDir: path.join(__dirname, "./static/"), // 设置文件上传目录
- keepExtensions: true, // 保持文件的后缀
- // 最大文件上传大小为512MB(如果使用反向代理nginx,需要设置client_max_body_size)
- maxFieldsSize: 512 * 1024 * 1024,
- },
- })
- );
- app.use(router.routes());
- wss.on("connection", function (ws) {
- let messageIndex = 0;
- ws.on("message", async function (data, isBinary) {
- console.log(data);
- const message = isBinary ? data : data.toString();
- if (JSON.parse(message).type !== "personData") {
- return;
- }
- const result = await Mperson.findAll();
- wss.clients.forEach((client) => {
- messageIndex++;
- client.send(JSON.stringify(result));
- });
- });
- ws.onmessage = (msg) => {
- ws.send(JSON.stringify({ isUpdate: false, message: "pong" }));
- };
- ws.onclose = () => {
- console.log("服务连接关闭");
- };
- ws.send(JSON.stringify({ isUpdate: false, message: "首次建立连接" }));
- });
- server.listen(APP_PORT, () => {
- const host = process.env.APP_REDIS_HOST;
- const port = process.env.APP_PORT;
- console.log(
- `环境:${
- process.env.NODE_ENV ? "开发环境" : "生产环境"
- },服务器地址:http://${host}:${port}/findExcerpt`
- );
- });
- module.exports = server;
复制代码 这里可以先不引入websocket和Mperson,这是后续发布内容时才会用到。
注:app.use(cors())必须在app.use(router.routes())之前,不然访问路由时会显示跨域。
3.在package.json中添加命令"dev": "set NODE_ENV=development && nodemon app.js",
然后就可以直接运行npm run dev启动服务了
mysql创建数据库建表
在服务器上开放mysql端口3306,还有接下来使用到的redis端口6379。
使用root连接数据库,可以看到所有的数据库。
注:sequelize6版本最低支持mysql5.7,虽然不会报错,但是有提示
mysql默认情况下不允许root直接连接,需要手动放开
- 进入mysql:mysql -uroot -p
- 使用mysql:use mysql;
- 授权给所有ip:GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456(密码)' WITH GRANT OPTION;
- 刷新权限:FLUSH PRIVILEGES;
redis使用密码远程连接后,就不需要在本地安装redis
redis默认也是不允许远程连接,需要手动放开。
- 使用find / -name redis.conf先找到redis配置文件
- 将bind 127.0.0.1修改为bind 0.0.0.0;
- 设置protected-mode no;
- 设置密码requirepass 123456密码123456替换为自己的。
- 进入src使用./redis-server ../redis.conf重新启动
使用sequelize-automate自动生成表模型
创建文件sequelize-automate.config.js,然后在package.json增加一条命令"automate": "sequelize-automate -c './sequelize-automate.config.js'",使用npm run automate自动生成表模型。
注:建议自己维护createdAt和updatedAt,因为在Navicat Premium 15上创建表后,自动生成的模型虽然会自动增加createdAt和updatedAt,但是不会同步到数据库上,需要删除数据库中的表,然后再重新启动服务才会同步,但是当id为主键且不为null时,模板会生成defaultValue为null,在个别表中会报错
- const Automate = require("sequelize-automate");
- const dbOptions = {
- database: "test",
- username: "test",
- password: "123456",
- dialect: "mysql",
- host: "1.15.42.9",
- port: 3306,
- logging: false,
- define: {
- underscored: false,
- freezeTableName: false,
- charset: "utf8mb4",
- timezone: "+00:00",
- dialectOptions: {
- collate: "utf8_general_ci",
- },
- timestamps: true, // 自动创建createAt和updateAt
- },
- };
- const options = {
- type: "js",
- dir: "./src/models",
- camelCase: true,
- };
- const automate = new Automate(dbOptions, options);
- (async function main() {
- const code = await automate.run();
- console.log(code);
- })();
复制代码 连接mysql和redis
mysql连接,timezone默认是世界时区, "+08:00"指标准时间加8个小时,也就是北京时间
- const { Sequelize } = require("sequelize");
- const { APP_DATA_BASE, APP_USERNAME, APP_PASSWORD, APP_DATA_HOST } = require("../config/index");
- const seq = new Sequelize(APP_DATA_BASE, APP_USERNAME, APP_PASSWORD, {
- host: APP_DATA_HOST,
- dialect: "mysql",
- define: {
- timestamps: true,
- },
- timezone: "+08:00"
- });
- seq
- .authenticate()
- .then(() => {
- console.log("数据库连接成功");
- })
- .catch((err) => {
- console.log("数据库连接失败", err);
- });
- seq.sync();
- // 强制同步数据库,会先删除表,然后创建 seq.sync({ force: true });
- module.exports = seq;
复制代码 redis连接,database不同环境使用不同的分片
- const Redis = require("redis");
- const {
- APP_REDIS_HOST,
- APP_REDIS_PORT,
- APP_REDIS_PASSWORD,
- APP_REDIS_DB
- } = require("../config");
- const client = Redis.createClient({
- url: "redis://" + APP_REDIS_HOST + ":" + APP_REDIS_PORT,
- host: APP_REDIS_HOST,
- port: APP_REDIS_PORT,
- password: APP_REDIS_PASSWORD,
- database: APP_REDIS_DB,
- });
- client.connect();
- client.on("connect", () => {
- console.log("Redis连接成功!");
- });
- // 连接错误处理
- client.on("error", (err) => {
- console.error(err);
- });
- // client.set("age", 1);
- module.exports = client;
复制代码 一切都没问题后,现在开始编写路由代码,也就是一个个接口
编写登录注册
在controllers新建user.js模块,具体逻辑:登录和注册是同一个接口,前端提交时,会先判断这个用户是否存在,不存在会将传来的密码进行加密,然后将用户信息存入到数据库,同时使jsonwebtoken颁发token,token本身是无状态的,也就是说,在时间到期之前不会销毁!这里可以在服务端维护一个令牌黑名单,用于退出登录。- const response = require("../utils/resData");
- const bcrypt = require("bcryptjs");
- const jwt = require("jsonwebtoken");
- const { APP_JWT_SECRET } = require("../config/index");
- const seq = require("../mysql/sequelize");
- const UserModel = require("../models/user");
- const Muser = UserModel(seq);
- // 类定义
- class User {
- constructor() {}
- // 注册用户
- async register(ctx, next) {
- try {
- const { userName: user_name, password: pass_word } = ctx.request.body;
- if (!user_name || !pass_word) {
- ctx.body = response.ERROR("userNotNull");
- return;
- }
- // 判断用户是否存在
- const isExist = await Muser.findOne({
- where: {
- user_name: user_name,
- },
- });
- if (isExist) {
- const res = await Muser.findOne({
- where: {
- user_name: user_name,
- },
- });
- // 密码是否正确
- if (bcrypt.compareSync(pass_word, res.dataValues.password)) {
- // 登录成功
- const { password, ...data } = res.dataValues;
- ctx.body = response.SUCCESS("userLogin", {
- token: jwt.sign(data, APP_JWT_SECRET, { expiresIn: "30d" }),
- userInfo: res.dataValues,
- });
- } else {
- ctx.body = response.ERROR("userAlreadyExist");
- }
- } else {
- // 加密
- const salt = bcrypt.genSaltSync(10);
- // hash保存的是 密文
- const hash = bcrypt.hashSync(pass_word, salt);
- const userInfo = await Muser.create({ user_name, password: hash });
- const { password, ...data } = userInfo.dataValues;
- ctx.body = response.SUCCESS("userRegister", {
- token: jwt.sign(data, APP_JWT_SECRET, { expiresIn: "30d" }),
- userInfo,
- });
- }
- } catch (error) {
- console.error(error);
- ctx.body = response.SERVER_ERROR();
- }
- }
- }
- module.exports = new User();
复制代码 增加校验用户是否登录的中间件
在middleware创建index.js,将用户信息挂载到ctx.state.user上,方便后续别的地方需要用到用户信息- const jwt = require("jsonwebtoken");
- const { APP_JWT_SECRET } = require("../config/index");
- const response = require("../utils/resData");
- // 上传文件时,如果存在token,将token添加到state,方便后面使用,没有或者失效,则返回null
- const auth = async (ctx, next) => {
- // 会返回小写secret
- const token = ctx.request.header["love-token"];
- if (token) {
- try {
- const user = jwt.verify(token, APP_JWT_SECRET);
- // 在已经颁发token接口上面添加user对象,便于使用
- ctx.state.user = user;
- } catch (error) {
- ctx.state.user = null;
- console.log(error.name);
- if (error.name === "TokenExpiredError") {
- ctx.body = response.ERROR("tokenExpired");
- } else if (error.name === "JsonWebTokenError") {
- ctx.body = response.ERROR("tokenInvaild");
- } else {
- ctx.body = response.ERROR("unknownError");
- }
- return;
- }
- }
- await next();
- };
- module.exports = {
- auth,
- };
复制代码 增加路由页面
将controllers所有文件都导入到index.js中,然后再暴露出去,这样在router文件就只需要引入controllers/index.js即可- const hotword = require("./hotword");
- const issue = require("./issue");
- const person = require("./person");
- const user = require("./user");
- const common = require("./common");
- const wallpaper = require("./wallpaper");
- const fileList = require("./fileList");
- const ips = require("./ips");
- module.exports = {
- hotword,
- issue,
- person,
- user,
- common,
- wallpaper,
- fileList,
- ips,
- };
复制代码- const router = require("koa-router")();
- const {
- hotword,
- person,
- issue,
- common,
- user,
- wallpaper,
- fileList,
- ips,
- } = require("../controllers/index");
- const { auth } = require("../middleware/index");
- // router.get("/", async (ctx) => {
- // ctx.body = "欢迎访问该接口";
- // });
- router.get("/wy/find", hotword.findHotword);
- router.get("/wy/pageQuery", hotword.findPageHotword);
- // 登录才能删除,修改评论(协商缓存)
- router.get("/findExcerpt", person.findExcerpt);
- router.get("/addExcerpt", person.addExcerpt);
- router.get("/updateExcerpt", auth, person.updateExcerpt);
- router.get("/delExcerpt", auth, person.delExcerpt);
- // 不走缓存
- router.post("/findIssue", issue.findIssue);
- router.post("/addIssue", issue.addIssue);
- router.post("/delIssue", issue.delIssue);
- router.post("/editIssue", issue.editIssue);
- router.post("/register/user", user.register);
- router.post("/upload/file", common.uploadFile);
- router.post("/paste/upload/file", common.pasteUploadFile);
- // router.get("/download/file/:name", common.downloadFile);
- // 强缓存
- router.get("/wallpaper/findList", wallpaper.findPageWallpaper);
- // 文件列表
- router.post("/file/list", auth, fileList.findFileLsit);
- router.post("/save/list", auth, fileList.saveFileInfo);
- router.post("/delete/file", auth, fileList.delFile);
- // ip
- router.post("/find/ipList", (ctx, next) => ips.findIpsList(ctx, next));
- module.exports = router;
复制代码注:需要用户登录的接口在路由前加auth中间件即可
获取ip、上传、下载
获取ip
获取ip可以单独提取出来,当作中间件,这里当用户访问我的ip地址页面时,会自动在数据库中添加记录,同时使用redis存储当前ip,10分钟内再次访问不会再增加。
注:当服务在线上使用nginx反向代理时,需要在app.js添加app.proxy = true,同时需要配置nginx来获取用户真实ip
- const response = require("../utils/resData");
- const { getClientIP } = require("../utils/common");
- const seq = require("../mysql/sequelize");
- const IpsModel = require("../models/ips");
- const MIps = IpsModel(seq);
- const client = require("../utils/redis");
- const { reqIp } = require("../api/ip");
- class Ips {
- constructor() {}
- async findIpsList(ctx, next) {
- try {
- if (!process.env.NODE_ENV) {
- await this.addIps(ctx, next);
- }
- const { size, page } = ctx.request.body;
- const total = await MIps.findAndCountAll();
- const data = await MIps.findAll({
- order: [["id", "DESC"]],
- limit: parseInt(size),
- offset: parseInt(size) * (page - 1),
- });
- ctx.body = response.SUCCESS("common", { total: total.count, data });
- } catch (error) {
- console.error(error);
- ctx.body = response.SERVER_ERROR();
- }
- }
- async addIps(ctx, next) {
- try {
- const ip = getClientIP(ctx);
- const res = await client.get(ip);
- // 没有才在redis中设置
- if (!res) {
- // 需要将值转为字符串
- await client.set(ip, new Date().toString(), {
- EX: 10 * 60, // 以秒为单位存储10分钟
- NX: true, // 键不存在时才进行set操作
- });
- if (ip.length > 6) {
- const obj = {
- id: Date.now(),
- ip,
- };
- const info = await reqIp({ ip });
- if (info.code === 200) {
- obj.operator = info.ipdata.isp;
- obj.address = info.adcode.o;
- await MIps.create(obj);
- } else {
- console.log("ip接口请求失败");
- }
- }
- }
- } catch (error) {
- console.error(error);
- }
- await next();
- }
- }
- module.exports = new Ips();
复制代码- location / {
- proxy_set_header Host $http_host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header REMOTE-HOST $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header Public-Network-URL http://$http_host$request_uri;
- proxy_pass http://127.0.0.1:40001;
- }
复制代码 上传
配置文件.env需要配置好绝对路径:APP_FILE_PATH = /任意位置- const fs = require("fs");
- const path = require("path");
- const crypto = require("crypto");
- const response = require("../utils/resData");
- const { APP_NETWORK_PATH, APP_FILE_PATH } = require("../config/index");
- class Common {
- constructor() {}
- async uploadFile(ctx, next) {
- try {
- const { file } = ctx.request.files;
- // 检查文件夹是否存在,不存在则创建文件夹
- if (!fs.existsSync(APP_FILE_PATH)) {
- APP_FILE_PATH && fs.mkdirSync(APP_FILE_PATH);
- }
- // 上传的文件具体地址
- let filePath = path.join(
- APP_FILE_PATH || __dirname,
- `${APP_FILE_PATH ? "./" : "../static/"}${file.originalFilename}`
- );
- // 创建可读流(默认一次读64kb)
- const reader = fs.createReadStream(file.filepath);
- // 创建可写流
- const upStream = fs.createWriteStream(filePath);
- // 可读流通过管道写入可写流
- reader.pipe(upStream);
- const fileInfo = {
- id: Date.now(),
- url: APP_NETWORK_PATH + file.originalFilename,
- name: file.originalFilename,
- size: file.size,
- type: file.originalFilename.match(/[^.]+$/)[0],
- };
- ctx.body = response.SUCCESS("common", fileInfo);
- } catch (error) {
- console.error(error);
- ctx.body = response.ERROR("upload");
- }
- }
- async pasteUploadFile(ctx, next) {
- try {
- const { file } = ctx.request.body;
- const dataBuffer = Buffer.from(file, "base64");
- // 生成随机40个字符的hash
- const hash = crypto.randomBytes(20).toString("hex");
- // 文件名
- const filename = hash + ".png";
- let filePath = path.join(
- APP_FILE_PATH || __dirname,
- `${APP_FILE_PATH ? "./" : "../static/"}${filename}`
- );
- // 以写入模式打开文件,文件不存在则创建
- const fd = fs.openSync(filePath, "w");
- // 写入
- fs.writeSync(fd, dataBuffer);
- // 关闭
- fs.closeSync(fd);
- const fileInfo = {
- id: Date.now(),
- url: APP_NETWORK_PATH + filename,
- name: filename,
- size: file.size || "",
- type: 'png',
- };
- ctx.body = response.SUCCESS("common", fileInfo);
- } catch (error) {
- console.error(error);
- ctx.body = response.ERROR("upload");
- }
- }
- }
- module.exports = new Common();
复制代码 下载
直接配置nginx即可,当客户端请求路径以 /static 开头的静态资源时,nginx 会从指定的文件系统路径 /www/wwwroot/note.loveverse.top/static 中获取相应的文件。用于将 /static 路径下的静态资源标记为下载类型,用户访问这些资源时告诉浏览器下载文件- location /static {
- add_header Content-Type application/x-download;
- alias /www/wwwroot/note.loveverse.top/static;
- }
复制代码 小程序登录和公众号验证
小程序
在.env中新增如配置,在middleware/index.js中增加refresh中间件- # 微信公众号appID
- APP_ID = wx862588761a1a5465
- # 微信公众号appSecret
- APP_SECRET = a06938aae54d2f72a41e4710854354534
- # 微信公众号token
- APP_TOKEN = 543543
- # 微信小程序appID
- APP_MINI_ID = wx663ca454434243
- # 微信小程序密钥
- APP_MINI_SECRET = ee17b15f95fcd597243243432432
复制代码- const refresh = async (ctx, next) => {
- // 将openId挂载到ids,供全局使用
- const token = ctx.request.header["mini-love-token"];
- if (token) {
- try {
- // -1说明没有设置过期时间,-2表示不存在该键
- const ttl = await client.ttl(token);
- if (ttl === -2) {
- ctx.body = response.WARN("token已失效,请重新登录", 401);
- return;
- }
- // 过期时间小于一个月,增加过期时间
- if (ttl < 30 * 24 * 60 * 60) {
- await client.expire(token, 60 * 24 * 60 * 60);
- }
- // 挂载openid
- const ids = await client.get(token);
- const info = ids.split(":");
- ctx.state.ids = {
- sessionId: info[0],
- openId: info[1],
- };
- } catch (error) {
- ctx.state.ids = null;
- console.error(error);
- ctx.body = response.ERROR("redis获取token失败");
- return;
- }
- } else {
- ctx.body = response.WARN("请先进行登录", 401);
- return;
- }
- await next();
- };
复制代码 公众号验证
安装xml2js,将包含 XML 数据的字符串解析为 JavaScript 对象传给公众号。在utils/constant.js添加常量,然后编写响应公众号的接口chat.js。这里公众号调试难免会需要将开发环境映射到公网,这里使用xshell隧道做内网穿透
- const template = `
- <xml>
- <ToUserName><![CDATA[<%-toUsername%>]]></ToUserName>
- <FromUserName><![CDATA[<%-fromUsername%>]]></FromUserName>
- <CreateTime><%=createTime%></CreateTime>
- <MsgType><![CDATA[<%=msgType%>]]></MsgType>
- <% if (msgType === 'news') { %>
- <ArticleCount><%=content.length%></ArticleCount>
- <Articles>
- <% content.forEach(function(item){ %>
- <item>
- <Title><![CDATA[<%-item.title%>]]></Title>
- <Description><![CDATA[<%-item.description%>]]></Description>
- <PicUrl><![CDATA[<%-item.picUrl || item.picurl || item.pic || item.thumb_url %>]]></PicUrl>
- <Url><![CDATA[<%-item.url%>]]></Url>
- </item>
- <% }); %>
- </Articles>
- <% } else if (msgType === 'music') { %>
- <Music>
- <Title><![CDATA[<%-content.title%>]]></Title>
- <Description><![CDATA[<%-content.description%>]]></Description>
- <MusicUrl><![CDATA[<%-content.musicUrl || content.url %>]]></MusicUrl>
- <HQMusicUrl><![CDATA[<%-content.hqMusicUrl || content.hqUrl %>]]></HQMusicUrl>
- </Music>
- <% } else if (msgType === 'voice') { %>
- <Voice>
- <MediaId><![CDATA[<%-content.mediaId%>]]></MediaId>
- </Voice>
- <% } else if (msgType === 'image') { %>
- <Image>
- <MediaId><![CDATA[<%-content.mediaId%>]]></MediaId>
- </Image>
- <% } else if (msgType === 'video') { %>
- <Video>
- <MediaId><![CDATA[<%-content.mediaId%>]]></MediaId>
- <Title><![CDATA[<%-content.title%>]]></Title>
- <Description><![CDATA[<%-content.description%>]]></Description>
- </Video>
- <% } else { %>
- <Content><![CDATA[<%-content%>]]></Content>
- <% } %>
- </xml>
- `;
- module.exports = { template };
复制代码- const crypto = require("crypto");
- const xml2js = require("xml2js");
- const ejs = require("ejs");
- const {
- template
- } = require("../utils/constant");
- const { APP_ID, APP_SECRET, APP_TOKEN } = require("../config/index");
- const response = require("../utils/resData");
- const wechat = {
- appID: APP_ID,
- appSecret: APP_SECRET,
- token: APP_TOKEN,
- };
- const compiled = ejs.compile(template);
- function reply(content = "", fromUsername, toUsername) {
- const info = {};
- let type = "text";
- info.content = content;
- if (Array.isArray(content)) {
- type = "news";
- } else if (typeof content === "object") {
- if (content.hasOwnProperty("type")) {
- type = content.type;
- info.content = content.content;
- } else {
- type = "music";
- }
- }
- info.msgType = type;
- info.createTime = new Date().getTime();
- info.fromUsername = fromUsername;
- info.toUsername = toUsername;
- return compiled(info);
- }
- class Wechat {
- constructor() {}
- // 公众号验证
- async verifyWechat(ctx, next) {
- try {
- let {
- signature = "",
- timestamp = "",
- nonce = "",
- echostr = "",
- } = ctx.query;
- const token = wechat.token;
- // 验证token
- let str = [token, timestamp, nonce].sort().join("");
- let sha1 = crypto.createHash("sha1").update(str).digest("hex");
- if (sha1 !== signature) {
- ctx.body = "token验证失败";
- } else {
- // 验证成功
- if (JSON.stringify(ctx.request.body) === "{}") {
- ctx.body = echostr;
- } else {
- // 解析公众号xml
- let obj = await xml2js.parseStringPromise(ctx.request.body);
- let xmlObj = {};
- for (const item in obj.xml) {
- xmlObj[item] = obj.xml[item][0];
- }
- console.info("[ xmlObj.Content ] >", xmlObj);
- // 文本消息
- if (xmlObj.MsgType === "text") {
- const replyMessageXml = reply(
- str,
- xmlObj.ToUserName,
- xmlObj.FromUserName
- );
- ctx.type = "application/xml";
- ctx.body = replyMessageXml;
- // 关注消息
- } else if (xmlObj.MsgType === "event") {
- if (xmlObj.Event === "subscribe") {
- const str = msg.attendion;
- const replyMessageXml = reply(
- str,
- xmlObj.ToUserName,
- xmlObj.FromUserName
- );
- ctx.type = "application/xml";
- ctx.body = replyMessageXml;
- } else {
- ctx.body = null;
- }
- // 其他消息
- } else {
- const str = msg.other;
- const replyMessageXml = reply(
- str,
- xmlObj.ToUserName,
- xmlObj.FromUserName
- );
- ctx.type = "application/xml";
- ctx.body = replyMessageXml;
- }
- }
- }
- } catch (error) {
- console.error(error);
- // 返回500,公众号会报错
- ctx.body = null;
- }
- }
- }
- module.exports = new Wechat();
复制代码注:公众号和小程序是另一个项目,所以不在github上
调试
以脚本命令的方式运行,可以看到完整的数据打印,排查问题更方便
存在的问题
- websocket服务端没封装,应该单独分出一个文件来写。
- ip地址记录只有访问ip列表这个页面才会增加,一直停留在其他页面无法获取ip记录
- pseron接口测试协商缓存不生效,返回304,但是浏览器还是显示200
- sequelize-automate自动生成的createdAt和updatedAt没有同步到数据库中
- 上传文件缓慢
等等还有其他一些未列举的问题
代码链接
https://github.com/loveverse/love-blog
参考文章
https://github.com/jj112358/node-api(nodejs从0到1更加详细版)
https://www.freesion.com/article/34551095837/(xshell内网穿透原理)
来源:https://www.cnblogs.com/loveverse/p/17661367.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|