1 Star 0 Fork 0

郭庆 / miniprogramServer

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 23.83 KB
一键复制 编辑 原始数据 按行查看 历史
郭庆 提交于 2023-05-11 20:47 . fuck

mini-program-server-master 一个关于网络热门资源的后台服务

包含哪些内容呢?

  1. 包含 各类电脑软件,手机软件
  2. 包含 各类电子文档
  3. 包含 各类教程资料
  4. 包含 常用工具

nodemon 与文件写入 冲突导致死循环

问题原因
在写后台服务之错误日志记录到json文件模块时遇到了上述问题;

nodemon默认监听全局的关联文件;
那此时操作读取写入文件的函数都引入了errLog.josn;

所以你使用nodemon新增记录时;
它就认为json文件变动了;
那么我重新执行js代码;
也就造成了;

写入日志函数重复执行, 循环反复;
解决办法

//
//配置nodemon的忽略规则;
//将.json文件包含到忽略列表中
//》》值得注意的是 配置的路径都是基于项目根目录
//》》如果你想表示任意文件夹下的某个文件请参考下方数组索引为零的示范

//在项目根目录下新建 nodemon.json 文件。配置 ignore 参数。

{
    "ignore"
:
    ["**/logs/*.json", "test/*", "docs/*", "*.test.js"]
}

//或者 修改nodemon npm包的pageage.json 文件,新增 nodemonConfig 字段

//尝试过好像不是很靠谱
{
    "name"
:
    "nodemon",
        "version"
:
    "1.0.0",
        "nodemonConfig"
:
    {
        "ignore"
    :
        ["test/*", "docs/*"]
    }
}

在 express 使用 npm 包中间件或者自己的中间件时写法问题

我注意到了并没有一个语法规范,大多数是一个函数返回一个函数;
外层的参数是中间件纯粹需要的,内层是标准的req,res,next 三个参数;
为了代码的美观性和书写规范
我首先考虑了这里书写自定义中间件时尽量与其他依赖包的写法一致;
但是为了注解 我又不得不只写一层
例如
function system(req, res, next) {
  res.removeHeader("X-Powered-By");
  res.set("Permanent-domain", req.hostname);
  next();
}

function system(options) {
  return function (req, res, next) {
    res.removeHeader("X-Powered-By");
    res.set("Permanent-domain", req.hostname);
    next();
  };
}

//以上两种前者
app.use(system);
//后者
app.use(system());

express 的参数获取

在 express 写服务端代码时,

接收参数一般在 req 中,

其中 body 对应 post 请求中的参数 , query 对应 get 请求中的参数, params 对应 路径参数 /:id

大前提是 express 在此 route 之前 use 过 urlencoded({extended:true}); (express 已经内置了 bodyParser 的 解析参数的功能 )

配置静态资源时需要注意 jwt 与静态路径的并列关系

express依赖包中也内置了 express-jwt 可以开箱即用
它封装了 jsonwebtoken ,可以直接当作中间件来使用
有时静态资源中定义好了 但是jwt会拦截 这时就需要配置unless
下边是示例
export declare type Params =
  | {
      method?: string | string[];
      path?: Path | Path[];
      ext?: string | string[];
      useOriginalUrl?: boolean;
      custom?: RequestChecker;
    }
  | RequestChecker;
//如果符合以上的任一条件 jwt中间件将会放行 所以配置完建议多测试

实现 noneFolderCreateFolderByUpLoad 遇到了路径的问题

在path对象中有不少函数 他们之间都有哪些细微的差别呢?
我们举几个常用的例子
let { join, resolve, __dirname, __filename } = path;
//__dirname 代表当前代码所在的目录路径
//__filename 代表当前代码所在的文件路径 包括扩展名
// join 会根据传入的参数 进行运算
join("..", ".."); //代表从当前目录向上翻两个目录
//resolve 搭配项目进程运行的目录 在我的项目里 就是 app.js 所在目录使用
resolve(process.cwd(), "./xx"); //他会往下接 值得注意的是 如果遇到 /开头 他会以这个为根往下接

multer 的应用 它是为了处理 body 数据而生的

在express中 它是一个函数 它的返回结果可以作为中间件使用,
调用它时传入一个对象,这个对象中可以描述你想让它为你如何处理表单数据
最重要的是文件,它会按你说的保存图片 类似下方代码
function noneFolderCreateFolderByUpLoad(req, file) {
  req.app.file = file;
  let realtiveDirPath = `/static/${dayjs().format("YYYYMMDD")}`;
  let fileNamePath =
    "/" +
    dayjs().format("HHmmss") +
    md5(file.originalname) +
    path.extname(file.originalname);

  let absoluteDirPath = path.join(process.cwd(), realtiveDirPath);

  if (!fs.existsSync(absoluteDirPath)) fs.mkdirSync(absoluteDirPath);

  return { realtiveDirPath, fileNamePath, absoluteDirPath };
}

const handleMulterConf = multer({
  storage: multer.diskStorage({
    destination: (req, file, cb) =>
      cb(null, noneFolderCreateFolderByUpLoad(req, file).absoluteDirPath),
    filename: (req, file, cb) =>
      cb(null, noneFolderCreateFolderByUpLoad(req, file).fileNamePath),
  }),
  fileFilter: (req, file, cb) => {
    cb(null, true);
  },
});
apiRouter.post("/upload", handleMulterConf.single("image"), handleUpload);
//handleUpload 是最后阶段构造返回对象的函数 可以忽略

练习 mysql

数据库的结构

数据库服务器可以有很多个数据库

一个数据库可以有很多个表 类似用户数据表 交易记录表

总之数据表里就存放着每一条(一行代表一条)数据,数据的属性就是列名

表与表之间可以关联

对数据库语句有四种分类
ddl 数据定义语言 对数据库或者表的创建删除修改
dml 数据操作语言 对数据表的添加删除修改
dql 对数据的查询 重点 这个分支有很多花样
dcl 对数据库的权限进行操作 数据库也可以进行访问控制

DML 数据操作(manipulate)语言

#创建数据表必须要知道的事情

#包裹列属性的是小括号 就像调用内置函数一样,别写成大括号了

#数据库中 每一列叫做 字段 就像说 user表中的username字段 不要说 ‘列’ 了

#这些列属性中必须且只能有一个是主键 也就是 在其中一个列上 添加 primary key 属性

#如果用到了 varchar() 字段属性 括号中要填超过255的数字就要另行配置的 会报错 [42000][1071]
#大概意思就是你定义的字段超过了 最大的允许字节  它不同意 具体百度

# 没涉及到具体的字段内容都属于ddl 数据定义语言
# 涉及到具体的字段内容 就属于 dml 数据操作语言

# --需要注意自定义字段也不可以带引号或者使用反引号 例如 => `username`
# --需要注意添加属性时也要知道是不是需要带括号 像varchar varbinary 都是要带括号滴
# -- 插入数据需注意数据格式,如果想使用默认值就使用default代替;
# -- insert into table (colum) values( val );
# -- 更新数据库的某些数据 则需要 带where 语句 否则会全部修改 那是部队的
# -- 系统也会提示你有风险
# -- update database.table set colum = 'val' where colum = '';
# -- 表中删除数据 跟查询语句很类似 省去了输入列名 删除肯定是整行 也就不需要指定列了
# -- delete from database.table where colum = '';
# 创建数据库 ddl
create database if not exists abc;
# 修改数据库 无

# 数据库也没什么好修改的 就那几个属性

# 删除数据库 ddl
drop database abc;
# 创建数据表 ddl
create table if not exists abc
(
    id       bigint auto_increment not null primary key,
    username varchar(255) unique   not null,
    password varchar(255) unique   not null
);
#查看表结构 ddl
desc songshu.goods;
# 修改数据表 ddl
alter table abc
    add username timestamp default current_timestamp on update;
# 修改数据表名 ddl
alter table mysql.def rename to songshu.def;
#修改表结构  dml
# 建议不要修改表结构.

# 添加一个数据列
alter table songshu.def
    add times datetime;
# 删除一个数据列
alter table songshu.def
    drop username;
# 修改一个数据列
alter table songshu.def
    change `password` `fuck` varbinary(255);
# 表中插入数据 dml
insert into songshu.def (id, username, password, time, content)
values (default, 'admin', 'wwqqedwe', default, '我是一个粉刷新粉刷本来及');
# 表中更新数据 dml
update songshu.def
set username = 'adminsss'
where id = 4;
# 表中删除数据 dml
delete
from songshu.def
where id = 4;

数据查询语言 dql

#查询数据 dql 带条件的查询
# 排序 (order by) 翻页 (limit num offset num) 简写 (limit a,b) 理解为 从第a个开始 查出来共计b个 常用
select *
from songshu.def
where id > 1
order by id desc
limit 10 offset 0;
# 模糊查询 like '% key %'常用
select *
from songshu.userbook
where userbookid like "%a%";
# 聚合函数
# 查询某些数据的平均值 需要注意用到了聚合函数 就不可以 单独使用列 因为 显示的数据不一定是 相同的 会有出错的概率
select areaname, avg(mobilephone1) as avg
from songshu.userbook
where areaname = '爱丁堡';
# 查询时 给结果分组 下边的语句会输出每栋楼 的相对应条件 也就是查询语句有了前提 先查出每个building 在每个building数据中计算出聚合函数的结果
select building, min(building), max(houseno), count(*), sum(mobilephone1)
from songshu.userbook
group by building;
# having 通常与group by 一起使用 代替了 where
select building, min(building), max(houseno), count(*), sum(mobilephone1)
from songshu.userbook
group by building
having building > 1
limit 7,2;

外键约束

# 外键约束  可以创建时添加 也可以后期添加

# 创建时添加
create table songshu.website
(
    id       integer primary key,
    username text default '0',
    foreign key songshu.user (id) references orders (id)
);
# 后期给已有的表添加 有了外键依赖其他表的字段 就会有个前提 就是 要有这个外键约束的表中字段 如果不存在则 不让添加
# 反过来讲 如果进行了外键约束关联 那么 你想要直接修改 被关联的字段值时 是不被允许的
alter table songshu.orders
    add foreign key (goods_id) references songshu.goods (goods_id);
update songshu.goods
set goods_id = 100
where goods_id = 1;
# 会提示不被外键约束规则允许
# 如何解决这个问题呢 可以选择使用当遇到更新或删除遇到外键约束时如何处理
#可以使用 on update cascade,on update set null
#另外还有两种是 默认不允许的设置 no action , restrict
alter table songshu.orders
    drop foreign key songshu.orders.外键约束的值
#删除一条外键约束规则
# 注意不需要有空格
alter table songshu.orders
    add foreign key (goods_id) references songshu.goods (goods_id) on update cascade on delete cascade;

多表查询

# 默认写法 就是 笛卡尔乘积 第一张表乘以第二张表的行数
select *
from orders,
     goods;
# 左连接查询 保证 左侧表都能查询出来
select *
from orders
         left join goods on goods.goods_id = orders.goods_id

# 误区 下边这句与第二句 效果相同 但执行逻辑不同 上边的性能好
# 所有订单数据 并带出 所关联的商品
select *
from orders
         join goods o on orders.goods_id = o.goods_id
select *
from orders,
     goods
where goods.goods_id = orders.goods_id;
# mysql 不支持全连接 使用 union 加左连接 右连接
select *
from orders
         right join goods g on g.goods_id = orders.goods_id
union
select *
from orders
         left join goods d on d.goods_id = d.goods_id

分层架构思想


# 多对多的两个表 需要一个中间表 存上各自的id 然后每列分别有对应的每张表的键约束
create table students
(
    id          integer auto_increment primary key,
    stu_no      bigint not null default '00000000',
    stu_name    text            default null,
    stu_age     tinyint         default null,
    stu_address text            default null
);

create table books
(
    id         integer auto_increment primary key,
    book_no    bigint         default 20230315,
    book_name  text           default null,
    book_price decimal(10, 2) default 3.25,
    book_score decimal(10, 1) default 4.8
);

create table stu_book_select
(
    id      integer auto_increment primary key,
    stu_id  integer not null,
    book_id integer not null,
    foreign key (stu_id) references songshu.students (id) on update cascade on delete cascade,
    foreign key (book_id) references songshu.books (id) on update cascade on delete cascade
);
show create table stu_book_select;
# 再次练习修改属性 这两个应该可以为null
alter table songshu.stu_book_select
    add foreign key (book_id) references books (id) on update cascade on delete cascade;
alter table songshu.stu_book_select
    add foreign key stu_book_select (stu_id) references students (id) on update cascade on delete cascade;

# 然后关系表是需要维护的 例如选课后就需要 记录到 关系表 就是将stuid 与 bookid新增到 关系表
# 查询示例

select stu_name, stu_age, book_name, book_price
from students
         left join stu_book_select as sbs on students.id = sbs.stu_id
         left join books b on sbs.book_id = b.id
where students.stu_name is not null
  and book_name is not null;
#若使用左查询 那就是 前表为主 后表为辅 例如 每个学生选了哪些书 不加筛选 那就是 书本列会有null 因为 可能有学生没选书
#若使用右查询 那就是 前表为辅 后表为主 例如 每本书被哪些学生选了 不加条件筛选那就是学生列会有 null 因为 可能这本书没有学生选

# 部分列转换为对象

select stu_id, json_object(col)
from students
# 如果使用的mysql5.6可能会报错在当前库中找不到此方法 我的解决办法是升级8.0
# 升级到8.0之后又会遇到一个问题 就是:

    [42000][1140] In aggregated query without
GROUP BY, expression #1 of
SELECT list contains nonaggregated column 'songshu.students.name';
this is incompatible with sql_mode=only_full_group_by;

# 这只是sql规则上变严格了,之前的语句不规范能弄懂你意思就不报错 现在不行了
# 解决办法就是把参与分组的列也加到group by 之后
select students.name,
       students.id,
       json_arrayagg(json_object('id', b.id, 'name', b.book_name, 'price', b.book_price))
from students
         left join stu2book s2b on s2b.stu_id = students.id
         left join books b on s2b.book_id = b.id
group by students.name, students.id;

使用 nodejs + mysql2 操作数据库

详见 jsonToMysqlSync.js
重点在于使用参数和 自己拼接字符串
字符串操作时 常用到的函数是 :
Object.values(obj);
Object.keys(obj);
arr.push(item);
arr.join(",");
arr.map((item) => `"${item}"`);

升级 mysql8.0 客户端后的后遗症

mysql8.0默认的加密方式 是 caching_sha2_password
但这种加密方式 node模块- mysql 不支持 需要修改
#这里最后的root就是你修改后的密码,根据自己的需求更改
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';
#刷新权限
FLUSH PRIVILEGES;

git 修改.gitignore 之后如何使其生效

git rm -r ./ --cached
# 关键是要清除缓存
git add .
git commit -m "commit_message"
git push

git 修复 bug 可以通过 tag 号来切换 但只能是最新的 tag

# last tag version number
git checkout v1.5.7
# 在此tag创建分支并起名
git checkout -b 'hot fix'
# 这时候就可以写代码了
# 写完代码之后
git commit  -a -m "git add . 与 commit 同时操作了"
#提交完 打个tag 最后一位代表修复bug 中间代表功能更新 第一位代表改版
git tag v1.5.8
# 打完tag 需要合并到主分支上 在这之前不要忘记了先把自己切换到主分支
git checkout master
# 将某个分支合并到主分支
git merge hotfix

分层架构

我明白我目前编写代码的风格不属于分层架构,但我明白分层架构的优点; 因为是自己的练手项目,更改结构会导致不必要的麻烦,所以在这里总结一下:

UI(表现层): 主要是指与用户交互的界面。
用于接收用户输入的数据和显示处理后用户需要的数据。

BLL:(业务逻辑层): UI层和DAL层之间的桥梁。
实现业务逻辑。业务逻辑具体包含:验证、计算、业务规则等等。

DAL:(数据访问层): 与数据库打交道。
主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,
同时将业务层处理的数据保存到数据库。
(当然这些操作都是基于UI层的。用户的需求反映给界面(UI),
UI反映给BLL,BLL反映给DAL,DAL进行数据的操作,操作后再一一返回,直到将用户所需数据反馈给用户)

使用非对称加密来生成 token

我们做 token 验证时经常会用到 jwt,这是一个很有名的用户加密授权工具; 但是美中不足的是其默认的加密方式是对称加密(hs256),这会导致 一旦 secretKey 泄露 黑客便可以根据前两段数据进行伪造签发 token; 这对于使用 jwt 加密工具的人来讲是非常不稳妥的,那既然这样 我们可以使用非对称加密(rs256)进行 token 的签发和验证!!! 利用 openssl 生成 private 与 public

//openssl macOS与git bash 自带 ,其他系统请手动安装
open git bash
openssl
#生成私钥文件到当前所在文件夹
genrsa -out private.key 1024
# 根据生成的私钥文件生成公钥文件到当前所在文件夹
rsa -in private.key -pubout -out public.key

配合 jwt 使用时需要将 options > algorithm 的配置改为 'RS256' (默认是 hs256) 签发时 algorithm 验证时 algorithms 以下是其支持的加密算法

export type Algorithm =
  | "HS256"
  | "HS384"
  | "HS512"
  | "RS256"
  | "RS384"
  | "RS512"
  | "ES256"
  | "ES384"
  | "ES512"
  | "PS256"
  | "PS384"
  | "PS512"
  | "none";

富文本编辑器

我使用的是 mavon editor 关于图片上传的部分功能 我使用的是后端 jwt 放开权限 也可考虑通过监听 img-err 错误进行 token 的填充

前端实现 token 持久化

mutations: {
  setToken(state, val);
  {
    state.token = val;
    window.addEventListener("beforeunload", function (evt) {
      evt.preventDefault();
      localStorage.getItem("token") == null
        ? localStorage.setItem("token", state.token)
        : void 0;
    });
    localStorage.removeItem("token");
    return state.token;
  }
}

前端 vue 组件中的$attrs与$listeners

这两个属性都是见名知义 自定义属性与自定义事件 组件中进行声明此属性的话不可使用语法糖

<el-button v-bind="$attrs" v-on="$listeners"></el-button>

这样写之后就可以在此组件中拿到父组件传过来的数据与事件了

前端的动态路由 /path/:params

在 router 的 routes 对象配置中,path 的后边如果 写了 /:a/:b 这类东西 那么在 url 访问此路由时 router 函数 就会将 / 后对相应的每层路径名当作参数 给到路由组件 这时 最靠后的静态路由组件中的 router-view 标签 就会充当 动态路由所代表的组件 例如

[
  {
    path: "/user/:type/:id",
    name: `userDetail`,
    props(route) {
      //这里可以传入$route
    },
    component: () => import("./src/components/userDetail.vue"),
  },
];

上方的 userDetail 组件 就会渲染到 router-view 中 并且 将相关参数 放到 route 对象的 params 中, 值得注意的是, 你无法使用 path 搭配 params 跳转到此路由,假设你现在是在 user 列表页你想要进入 user 详情页


this.$router.push({
    name: 'goods_edit_dong_tai',
    params: {
        type: row.goods_id,
        id: row.goods_name,
        row
    }

/**
 * http://localhost:2699/goods/edit/101/101
 */

正如示例的一样 你通过 name 和 params 才能跳转 (通过 name 和 query 不行, 通过 path 和 params 也不行), 通过 name 和 params 时 其中的 params 参数,属于路由声明过的属性的话 会自动填充到对应的 path 中,组成 url

nodejs 中的 orm

使用 nodejs-orm 这个 npm 包

使用起来比较简单 会损失一点点性能 小型项目无伤大雅

jsonp 跨域

express 中可以使用 jsonp 来解决跨域 原理就是前端写好函数 给后端返回一个调用函数的入口 (就像写了个回调函数等待被后端调用,函数调用时可以传菜,就实现了数据的传递)

express engine

express 最原始的时候是服务端渲染 一些 seo 友好的站点都是采用此功能 我们需要一些简单的配置使 express 的实例中的 res 中具备 render 函数,我也会介绍它的用法 首先是设置

const app = require("express")();
// 实例化express

app.engine("html", require("express-art-template"));
// 设置html的处理函数,也是自定义模板处理引擎

app.set("views", path.join(__dirname, "../views"));
//绑定模板目录

app.set("views engine", "html");
// 设置实例的模板引擎为 html处理函数

这样我们的实例就具备了 res 调用 render 函数的能力 那么实现路径就是这样

app.get('/home', function (req, res) {
    // ...some thing code
    let paramsObject = {...}
    // 传入html的参数对象
    res.render('/home.html', paramsObject, ? callback)
    // render函数参数介绍
    // 1 模板函数相对于views的路径
    // 2 将会传入html供模板语法调用的参数对象
    // 3 回调函数,会即将给你两个实参 err, html
    // 值得一提的是 若callback render将不负责返回数据 你需要自己在回调中res.send(html)
    // 像下边这样
    res.render('index.html', {name: 'jack'}, (err, html) => {
        if (err) return res.send(err);
        res.send(html)
    });
    // use data you need code {{ variable }} stu link to https://juejin.cn/post/7195018707435782201
})

模板语法示例

<h1>{{name}}</h1>

<!--支持像逻辑判断 循环 之类的语法 具体参照art模板语法-->

art-template 与 layui 的 语法冲突解决

如果模版默认的 {{ }} 分隔符与你的其它模板(一般是服务端模板)存在冲突,你也可以重新定义分隔符:

laytpl.config({
  open: "<%",
  close: "%>",
});

//分割符将必须采用上述定义的
laytpl(
  [
    '<%# var type = "公"; %>', //JS 表达式
    "<% d.name %>是一位<% type %>猿。",
  ].join("")
).render(
  {
    name: "贤心",
  },
  function (string) {
    console.log(string); //贤心是一位公猿
  }
);

使用sqlite + sequelize 集成 nodejs

关于express中间件的生命周期

直接app.use() 使用的中间件是每次请求都会执行的中间件; 不要与 只是运行时执行一次的函数混淆

dist 打包后 上线的最佳解决方案

使用 express-static 建立一个静态访问服务 路径指向打包的文件夹 并且调整vue.config.js中的 publicPath

publicPath: process.env.NODE_ENV === 'production' ? '/' : '/',

因为我这里直接使用根目录作为后台服务 所以静态文件路径就是 根目录

rm -rf 命令

#删除当前所在目录下所有内容
sudo rm -rf ./*
sudo rm -rf .*  
# 克隆到当前所在目录 
git clone <仓库地址> ./
# 安装依赖
npm i
# 查看端口占用情况
lsof -i :2698
# 找到进程所在pid kill
kill pid

#主要用在node中 想要留下package.json
#无法实现

vim 编辑器教程

刚进入属于命令模式 i键切换到输入模式 x删除当前光标字符 :底线输入命令

常用流程

vim 文件路径/文件名 看到文件内容 键入i || a || o(会新起一行) 切换到输入模式 (界面左下角有个--insert--)常规编辑 backspace前删 del后删 ...... 编辑完 键入 esc 退出编辑模式 进入 命令模式 键入: 进入底线命令模式 常用 :wq w代表保存 q代表离开 以回车结束命令

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
NodeJS
1
https://gitee.com/guoqingqing123543/miniprogram-server.git
git@gitee.com:guoqingqing123543/miniprogram-server.git
guoqingqing123543
miniprogram-server
miniprogramServer
master

搜索帮助