项目中使用的的是 vite 4.x
的版本
使用 yarn
执行
yarn create vite
输入项目名称,选择 Vue
, TypeScrip
按照提示执行以下命令即可
cd dandgangshucheng
yarn
yarn dev
启动后的初始页面如下
打开项目,发现 main.ts
有报错
回想起来以前的工程里面有一个 shims-vue.d.ts
是用来解决这个的,但是现在的工程里面没有这个文件了, 取而代之的是一个 vite-env.d.ts
的文件,但是这个文件里并没有 shims-vue.d.ts
的内容。所以我需要手动添加一下。
// vite-env.d.ts
declare module "*.vue" {
import type { DefineComponent } from "vue";
const vueComponent: DefineComponent<{}, {}, any>;
export default vueComponent;
}
这样就不会报错,并且有类型提示了。
按理来说 Vite4.x
的版本不应该出现这种问题,可能是我哪里的配置出了问题,有了解的小伙伴可以在评论区指教一二,在此谢过了。
后来发现安装了 TypeScript Vue Plugin (Volar)
这个 vscode
的插件就不会报错 , 但是鼠标移到 App
上并没有出现类型提示。
// vite-env.d.ts 或者 env.d.ts
// 解决引入vue自定义组件报错的问题
declare module '*.vue' {
import type { ComponentOptions } from 'vue';
const component: ComponentOptions | ComponentOptions['setup'];
export default component;
}
如果需要在开发环境启动后 , 自动打开浏览器 , 需要添加 --open
// package.json
"scripts": {
"dev": "vite --open",
},
使用 import.meta.env
来获取环境变量
默认的环境变量有五个
通过在根目录添加以下文件来自定义环境变量
只有以
VITE_
为前缀的变量才可以在程序中使用
如果需要在 TS
中获取类型提示 , 要在vite-env.d.ts
文件中扩展类型
// vite-env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string;
// 更多环境变量...
}
这是默认的配置文件,可以看到默认导出的是一个对象,这样的弊端就是不够灵活,不可以在代码中输入 console.log
,也没有办法获取到当前的模式(MODE)。
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()],
})
修改成下面这样的函数形式,就可以从回调参数中获取到当前运行的模式,然后根据模式生成不同的配置对象。
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig(({ mode }) => {
console.log("🚀🚀 ~ mode", mode);
return {
plugins: [vue()],
};
});
运行一下程序,可以看到控制台已经输出了当前的模式 development
,获取到这个变量,后续就可以灵活的进行配置了。
根据回调参数中的 mode
属性,拼接上本地文件的前缀名,就可以拿到整个环境变量文件的名称了。
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig(({ mode }) => {
const envFilePrefix: string = ".env.";
const curEnvFileName = `${envFilePrefix}${mode}`;
console.log("🚀🚀 ~ curEnvFileName", curEnvFileName);
return {
plugins: [vue()],
};
});
运行一下程序,可以看到控制台已经输出了当前模式对应的文件 .env.development
,获取到这个文件名后,就可以读取里面的环境变量了。
实现这个功能,需要安装第三方库 dotenv
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import fs from "fs";
import dotenv, { DotenvParseOutput } from "dotenv";
export default defineConfig(({ mode }) => {
// 定义文件前缀
const envFilePrefix: string = ".env.";
// 获取当前模式下对应的环境变量文件
const curEnvFileName = `${envFilePrefix}${mode}`;
// 读取环境变量文件
const envData = fs.readFileSync(curEnvFileName);
// 把读取到的结果解析成对象
const envMap: DotenvParseOutput = dotenv.parse(envData);
console.log("🚀🚀 ~ envMap", envMap);
return {
plugins: [vue()],
};
});
另外,需要对 dotenv
的类型进行增强,不然我们获取不到自己定义的类型
// src\types\dotenv.d.ts
import "dotenv";
declare module "dotenv" {
export interface DotenvParseOutput {
VITE_HOST: string;
VITE_PORT: number;
VITE_BASE_URL: string;
VITE_PROXY_DOMAIN: string;
}
}
运行程序,可以看到环境变量就以对象的形式输出了
在环境变量文件 .env.development
里面配置好相关的变量
// .env.development
VITE_HOST = '127.0.0.1'
VITE_PORT = 3000
VITE_BASE_URL = '/dang'
VITE_PROXY_DOMAIN = 'http://192.168.2.6:5003/'
修改配置文件,到这里就完成了在配置文件中使用环境变量的全部步骤,后面其它的配置就可以灵活的在环境变量文件中进行添加和修改了。
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import fs from "fs";
import dotenv, { DotenvParseOutput } from "dotenv";
export default defineConfig(({ mode, command }) => {
// 获取当前的模式
console.log("🚀🚀 ~ 打包编译阶段还是编码阶段", command);
console.log("🚀🚀 ~ 当前在什么环境运行项目", mode);
// 定义文件前缀
const envFilePrefix: string = ".env.";
// 获取当前模式下对应的环境变量文件
const curEnvFileName = `${envFilePrefix}${mode}`;
// 读取环境变量文件
const envData = fs.readFileSync(curEnvFileName);
// 把读取到的结果解析成对象
const envMap: DotenvParseOutput = dotenv.parse(envData);
return {
plugins: [vue()],
server: {
host: envMap.VITE_HOST,
port: envMap.VITE_PORT,
proxy: {
[envMap.VITE_BASE_URL]: {
target: envMap.VITE_PROXY_DOMAIN,
},
},
},
};
});
其实 Vite
内置一个 loadEnv
方法, 也可以实现同样的功能,但是目前对 TS
的支持不太友好,返回的是一个 Record<string,string>
类型,不能获得代码的自动提示。
import { defineConfig, loadEnv } from "vite";
export default defineConfig(({ command, mode }) => {
// 根据当前工作目录中的 `mode` 加载 .env 文件
// 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
const env = loadEnv(mode, process.cwd(), "");
return {
// vite 配置
define: {
__APP_ENV__: env.APP_ENV,
},
};
});
在 src/utils
目录下新建一个工具类 imgUtil.ts
, 然后调用 Vite
内置的 glob
方法, 获取 assets
目录下的所有图片
src\utils\imgUtil.ts
export class ImgUtil {
static loadAllImg() {
const imgMap = import.meta.glob("../assets/**/*.png", { eager: true });
console.log("🚀🚀 ~ imgUtil ~ imgMap", imgMap);
}
}
结果是这样的
需要安装第三方库 good-storage
yarn add good-storage -S
编码实现
// src\utils\imgUtil.ts
/**
* 动态管理图片
*/
import goodStorage from "good-storage";
export class ImgUtil {
// 声明图片列表
static imgList: Record<string, string> = {};
// 缓存图片列表
static storageImgList() {
this.imgList = goodStorage.get("imgList") || {};
if (this.isEmpty()) {
this.loadAllImg();
goodStorage.set("imgList", this.imgList);
}
}
// 判断图片列表是否为空
static isEmpty() {
return !Object.getOwnPropertyNames(this.imgList).length;
}
// 获取图片
static getImg(imgName: string) {
return ImgUtil.imgList[imgName];
}
// 加载全部图片
static loadAllImg() {
// 获取项目中所有图片
const imgMap: Record<string, any> = import.meta.glob("../assets/**/*.png", {
eager: true,
});
// 声明绝对路径
let absolutePath: string = "";
// 声明图片名称
let imgName: string = "";
// 遍历所有图片
for (const path in imgMap) {
if (Object.prototype.hasOwnProperty.call(imgMap, path)) {
// 获取图片的相对路径
absolutePath = imgMap[path].default;
if (absolutePath) {
// 获取图片的名称
imgName = absolutePath.substring(absolutePath.lastIndexOf("/") + 1);
// 图片列表赋值 key:文件名 value:文件的相对路径
this.imgList[imgName] = absolutePath;
}
}
}
}
}
// main.ts
import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
import { ImgUtil } from "./utils/imgUtil";
// 缓存图片
ImgUtil.storageImgList();
createApp(App).mount("#app");
// App.vue
<script setup lang="ts">
import { ImgUtil } from './utils/imgUtil'
</script>
<template>
<div>
<img :src="ImgUtil.getImg('浏览器1.png')" />
</div>
</template>
好处:
坏处:
好处是可采用的,坏处是可优化的,还需要开动脑筋实现一个完美的解决方案,但是目前来说本人认为并没有很好用
yarn add eslint -D
yarn eslint --init
命令执行过后的默认文件如下
// .eslintrc.cjs
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true
},
extends: [
'plugin:vue/vue3-essential',
'standard-with-typescript'
],
overrides: [
],
parserOptions: {
ecmaVersion: 'latest'
},
plugins: [
'vue'
],
rules: {
}
}
刚初始化完成后,代码是有报错的,需要添加
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser'
},
添加了几个个人的代码风格,最终的文件内容如下
module.exports = {
// eslintrc.js 文件所在的目录为 root 目录
// eslint 规则将对这个目录以及该目录下的所有文件起作用
root: true,
// 让 Vue3.2 中的这些全局函数能正常使用
globals: {
defineProps: 'readonly',
defineExpose: 'readonly',
defineEmits: 'readonly',
withDefaults: 'readonly',
},
env: {
browser: true,
commonjs: true,
es2021: true,
},
// 继承别人写好的规则
extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],
overrides: [],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
},
// 插件的作用是对规则进行补充
plugins: ['vue'],
// 自定义的规则, 重写继承的规则
rules: {
// 关闭函数名后面必须有空格的验证
'space-before-function-paren': 0,
// 关闭强制不变的变量使用 const, 因为自动格式化 有时候会把 let 变成 const
'perfer-const': 0,
// 允许行尾分号
semi: 0,
// 允许尾后逗号
'comma-dangle': 0,
},
};
prettier
是按照 eslint
的规范进行格式化的工具,如果冲突则 prettier
优先级高
安装 vscode
的 prettier
插件 ,无需在项目中安装 prettier
然后找到设置中的 prettier
插件 ,可以进行傻瓜式配置
在可以在项目根目录下新建 .prettierrc
文件,优先级高于手动配置的内容
在项目根目录下新建 .vscode/setting.json
文件,写入以下内容,即可在保存代码的时候自动按照 eslint
和 prettier
的规范进行代码格式化
// 需要 vscode 安装 Prettier - Code formatter 扩展
{
// 控制编辑器是否自动格式化粘贴的内容。格式化程序必须可用,并且能针对文档中的某一范围进行格式化
"editor.formatOnPaste": true,
// 在保存时格式化文件。格式化程序必须可用,延迟后文件不能保存,并且编辑器不能关闭。
"editor.formatOnSave": true,
// 控制编辑器在键入一行后是否自动格式化该行。
"editor.formatOnType": false,
// 当编辑器失去焦点时,将自动保存未保存的编辑器。
"files.autoSave": "onFocusChange",
//在一定数量的字符后显示标尺
"editor.rulers": [100],
// 定义一个默认格式化程序, 该格式化程序优先于所有其他格式化程序设置。必须是提供格式化程序的扩展的标识符。
"editor.defaultFormatter": "esbenp.prettier-vscode",
// 忽略单词
"cSpell.words": ["vite"]
}
tsconfig.json
添加 paths
字段 。
// tsconfig.json
{
"compilerOptions": {
// 设置别名
"paths": {
"@/*": ["src/*"]
}
},
}
vite.config.ts
添加 resolve.alias
字段
// vite.config.ts
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
👉👉 本地安装最新版 MySQL 数据库
yarn init -y
yarn add
@types/koa @types/koa-json @types/koa-router
koa koa-body koa-json koa-router
nodemon ts-node typescript
// package.json
"scripts": {
"dev": "nodemon --watch src/ -e ts --exec ts-node ./src/app.ts"
},
// src\app.ts
import koa from 'koa';
import json from 'koa-json';
import Router from 'koa-router';
import body from 'koa-body';
const app = new koa();
const router = new Router();
// 定义路由前缀 , 一级路径
router.prefix('/dang');
app.use(json());
app.use(body());
router.get('/test', async (ctx: koa.Context, next: koa.Next) => {
ctx.body = '第一个请求 test';
});
app.use(router.routes());
app.listen(3002);
console.log('程序运行在3002端口');
新建 src\router\user.ts
// src\router\user.ts
import { Context } from 'koa';
import Router from 'koa-router';
const router = new Router();
// 定义模块前缀
router.prefix('/usermodule');
// get 请求
router.get('/findUserInfo/:username', async (ctx: Context) => {
const { username } = ctx.params;
ctx.body = `欢迎${username}`;
});
// post 请求
router.post('/addUser', async (ctx: Context) => {
const { username } = ctx.request.body;
ctx.body = `欢迎${username}`;
});
export default router;
app.js
中追加以下代码
// app.ts
import koa from 'koa';
import json from 'koa-json';
import Router from 'koa-router';
import body from 'koa-body';
import userRouter from './router/user';
const app = new koa();
const router = new Router();
// 定义路由前缀 , 一级路径
router.prefix('/dang');
app.use(json());
app.use(body());
// 追加模块路由
router.use(userRouter.routes(), userRouter.allowedMethods());
app.use(router.routes());
app.listen(3002);
console.log('程序运行在3002端口');
每添加一个路由模块, 都需要在 app.js
里面导入,然后注册,这样模块多了以后 app.js
就会变得非常臃肿,并且不好维护,所以最好能利用代码自动加载路由。
新建一个文件用来动态加载路由
// src\common\RouterLoader.ts
import path from 'path';
import fs from 'fs';
import Router from 'koa-router';
import Koa from 'koa';
import json from 'koa-json';
import body from 'koa-body';
/**
* @description: 动态加载路由
*/
class RouterLoader {
app!: Koa;
static routerLoader: RouterLoader = new RouterLoader();
init(app: Koa) {
this.app = app;
const router = this.loadAllRouter();
this.app.use(router.routes());
this.listen();
}
/**
* @description: 动载加载路由模块
* @return {*}
*/
loadAllRouter() {
// 获取一级路由
const rootRouter = this.getRootRouter();
// 获取所有路由文件的绝对路径
const filePaths = this.getAbsoluteFilePaths();
// 加载所有的二级路由到一级路由
filePaths.forEach((filePath) => {
const module = require(filePath);
if (this.isRouter(module)) {
rootRouter.use(module.routes(), module.allowedMethods());
}
});
return rootRouter;
}
/**
* @description: 获取一级路由
* @return {*}
*/
getRootRouter() {
const rootRouter = new Router();
// 定义路由前缀 , 一级路径
rootRouter.prefix('/dang');
this.app.use(json());
this.app.use(body());
return rootRouter;
}
/**
* @description: 判断引入的模块是否是路由模块
*/
isRouter(data: any): data is Router {
return data instanceof Router;
}
/**
* @description: 获取目录下所有的文件名称
* @param {string} dir 文件目录
* @return {string[]} 包含目录下所有文件的名称的数组
*/
getFileNames(dir: string) {
return fs.readdirSync(dir);
}
/**
* @description: 获取所有文件的绝对路径
* @return {string[]} 包含获取所有文件的绝对路径的数组
*/
getAbsoluteFilePaths() {
// 获取路由文件所在目录
const dir = path.join(process.cwd(), '/src/router');
// 获取所有的文件名称
const allFiles = this.getFileNames(dir);
// 拼接所有文件的绝对路径
const allFullFilePaths: string[] = [];
allFiles.forEach((file) => {
allFullFilePaths.push(`${dir}${path.sep}${file}`);
});
return allFullFilePaths;
}
listen() {
this.app.listen(3002);
console.log('程序运行在3002端口');
}
}
export default RouterLoader.routerLoader;
这样重构以后 , app.js
里面就可以清理一下了, 添加路由的话也不用修改文件的内容了。
import koa from 'koa';
import routerLoader from './common/RouterLoader';
const app = new koa();
// 调用动态加载路由
routerLoader.init(app);
在路由中使用 try catch
是非常普遍的一件事,但是写多个同样的代码就很不友好,所以既然 try catch
的格式是固定的,可以封装一个全局的异常处理中间件。
// src\common\GlobalException.ts
import type { Context, Next } from 'koa';
/**
* @description: 全局异常处理中间件
*/
const globalException = async (ctx: Context, next: Next) => {
try {
await next();
} catch (error) {
const err = error as Error;
ctx.body = `服务器错误${err.message}`;
}
};
export default globalException;
在 app.js
中注册
import koa from 'koa';
import routerLoader from './common/RouterLoader';
import globalException from './common/GlobalException';
const app = new koa();
// 注册全局异常中间件
app.use(globalException);
// 调用动态加载路由
routerLoader.init(app);
精简封装响应成功和响应失败
enum Code {
SUCCESS = 200,
SERVER_ERROR = 500,
}
class ResResult {
static success(data: any = undefined, msg: any = '') {
const code: Code = Code.SUCCESS;
return { data, msg, code };
}
static fail(msg: any = '') {
const code: Code = Code.SERVER_ERROR;
return { undefined, msg, code };
}
}
export const { success, fail } = ResResult;
这里主要学习到函数重载的知识,以及在 TS
中判断变量是否符合类型的写法。
// src\config\db.ts
interface DbConf {
host: string;
user: string;
password: string;
port: number;
database: string;
}
interface EnvConf {
dev: DbConf;
prod: DbConf;
}
class Conf {
static conf: Conf = new Conf();
env!: keyof EnvConf;
envConf!: EnvConf;
constructor() {
this.env = process.env.NODE_ENV === 'prod' ? 'prod' : 'dev';
this.initConf();
}
// 初始化配置
initConf() {
this.envConf = {
dev: {
host: 'localhost',
user: 'admin',
password: '123456',
port: 3306,
database: 'dangdang',
},
prod: {
host: 'www.xxx.com',
user: 'root',
password: '123456',
port: 3306,
database: 'dangdang',
},
};
}
// 函数重载实现获取 db 连接配置
getConf(): DbConf;
getConf(key: string): DbConf;
getConf(key: any = ''): any {
if (this.isDbConfKeys(key) && key.length > 0) {
return this.envConf[this.env][key];
} else {
return this.envConf[this.env];
}
}
// 判断是不是符合要求的类型
isDbConfKeys(key: any): key is keyof DbConf {
return ['host', 'user', 'password', 'database', 'port'].includes(key);
}
}
export default Conf.conf;
ORM
就是为了避免直接编写 sql
语句带来的繁琐,而把关系型数据表数据直接映射为 js
对象进行查询,同时也能把 js
对象 转换为关系型数据表的数据进行增加,修改或删除
Sequelize
是一个基于 promise
的 Node.js
ORM
,支持 MySQL
。
支持事务。支持一对一,一对多,多对一,多对多,关联表的映射。
主要学习 Sequelize
的查询语法, 以及 Dao
, model
的封装。
// src\modules\BaseDao.ts
/*
* @Author: 一尾流莺
* @Description:连接数据库 Dao 层
* @Date: 2023-02-21 17:14:07
* @LastEditTime: 2023-02-22 15:26:27
* @FilePath: \dang-be\src\modules\BaseDao.ts
*/
import dbConfig from '../config/db';
import { type Dialect } from 'sequelize';
import { Sequelize } from 'sequelize-typescript';
import path from 'path';
class BaseDao {
static baseDao: BaseDao = new BaseDao();
sequelize!: Sequelize;
constructor() {
this.initSeqConf('mysql');
}
initSeqConf(dialect: Dialect) {
const { host, user, password, database, port } = dbConfig.getConfig();
this.sequelize = new Sequelize(database, user, password, {
host,
port,
dialect, // 表示是何种数据库
define: {
timestamps: false, // true 表示给模型加上时间戳属性 (createAt,updateAt),false 标识不带时间戳属性
freezeTableName: true, // true 标识使用给定的表名, false 标识模型后名加s作为表名
},
});
}
addModels() {
const modelPath = path.join(process.cwd(), '/src/modules/decorModel');
this.sequelize.addModels([modelPath]);
}
}
const baseDao = BaseDao.baseDao;
baseDao.addModels();
export const { sequelize } = baseDao;
// src\modules\userinfo\model\index.ts
import { sequelize } from '../../BaseDao';
import { DataTypes } from 'sequelize';
class Userinfo {
static createModel() {
const model = sequelize.define(
'userinfo',
{
userid: {
type: DataTypes.INTEGER,
field: 'userid', // 不写默认使用属性名
primaryKey: true,
autoIncrement: true,
},
username: {
type: DataTypes.STRING(30),
field: 'username',
allowNull: false,
},
pwd: {
type: DataTypes.STRING(30),
field: 'pwd',
allowNull: false,
},
address: {
type: DataTypes.STRING(50),
field: 'address',
allowNull: true,
},
valid: {
type: DataTypes.TINYINT,
field: 'valid',
allowNull: true,
},
},
// {
// timestamps: false, // true 表示给模型加上时间戳属性 (createAt,updateAt),false 标识不带时间戳属性
// freezeTableName: true, // true 标识使用给定的表名, false 标识模型后名加s作为表名
// },
);
// model.sync({ force: false, alter: true }).catch(() => {}); // force true 强制同步数据表
return model;
}
}
export const model = Userinfo.createModel();
// src\modules\userinfo\dao\index.ts
import { Sequelize } from 'sequelize-typescript';
import { Op } from 'sequelize';
import { model } from '../model';
export interface Userinfo {
userid: number;
username: string;
psw: string;
address: string;
valid: number;
}
class UserDao {
static userDao: UserDao = new UserDao();
// 添加数据
async addUser(userinfo: Userinfo) {
// 不用 any 会报错, 不会改
return await model.create(userinfo as any);
}
// 查询所有数据
async findAllUser() {
return await model.findAll({ raw: true });
}
// 只查询部分属性
async findByProps() {
return await model.findAll({
raw: true,
attributes: ['username', 'pwd'],
});
}
// 精确查询
async findByUsernameAndPsw(username: string, psw: string) {
return await model.findOne({
raw: true,
where: {
[Op.or]: [
{
username,
},
{
psw,
},
],
},
});
}
// 模糊查询
async findByLike(key: string) {
const searchKey = `%${key}%`;
return await model.findAll({
raw: true,
where: {
username: {
[Op.like]: searchKey,
},
},
});
}
// 模糊查询 or
async findByUsmAndAddr() {
return await model.findAll({
raw: true,
where: {
[Op.or]: [
{
username: {
[Op.like]: '%王',
},
},
{
address: '武汉',
},
],
},
});
}
// 聚合查询
async countUserInfo() {
return await model.findAll({
raw: true,
group: 'address',
attributes: ['address', [Sequelize.fn('count', Sequelize.col('valid')), '总人数']],
where: {
valid: 1,
},
});
}
// 分页查询
async findUserWithPager(limit: number, offset: number) {
return await model.findAll({
raw: true,
limit: 3,
offset: 5,
});
}
}
export const userDao = UserDao.userDao;
import { type Context } from 'koa';
import Router from 'koa-router';
import { success } from '@/common/ResResult';
import { userDao, type Userinfo } from '@/modules/userinfo/dao';
const router = new Router();
// 定义模块前缀
router.prefix('/usermodule');
// 根据用户名密码单个查询用户信息
router.get('/findUserinfo/:username/:psw', async (ctx: Context) => {
const { username } = ctx.params;
ctx.body = success(`欢迎${username}`);
});
// 添加用户
router.post('/addUser', async (ctx: Context) => {
const userinfo: Userinfo = ctx.request.body;
const dbUserinfo = await userDao.addUser(userinfo);
ctx.body = success(dbUserinfo);
});
// 查询全部用户
router.get('/findAllUser', async (ctx: Context) => {
const allUser = await userDao.findAllUser();
ctx.body = success(allUser);
});
// 只查看部分属性
router.get('/findByProps', async (ctx: Context) => {
const allUser = await userDao.findByProps();
ctx.body = success(allUser);
});
// 用户名密码精确查询
router.get('/findByUsernameAndPsw/:username/:psw', async (ctx: Context) => {
const { username, psw } = ctx.params;
const allUser = await userDao.findByUsernameAndPsw(username, psw);
ctx.body = success(allUser);
});
// 模糊查询
router.get('/findByLike/:key', async (ctx: Context) => {
const { key } = ctx.params;
const allUser = await userDao.findByLike(key);
ctx.body = success(allUser);
});
// 模糊查询 or
router.get('/findByUsmAndAddr', async (ctx: Context) => {
const allUser = await userDao.findByUsmAndAddr();
ctx.body = success(allUser);
});
// 聚合查询
router.get('/countUserInfo', async (ctx: Context) => {
const allUser = await userDao.countUserInfo();
ctx.body = success(allUser);
});
// 分页查询
router.get('/findUserWithPager/:pageNo/:pageSize', async (ctx: Context) => {
const { pageNo, pageSize } = ctx.params;
const offset = (pageNo - 1) * pageSize;
const allUser = await userDao.findUserWithPager(offset, pageSize);
ctx.body = success(allUser);
});
export default router;
module.exports = router;
当一个网站并发量过高,假设网站一天上万的访问量,后端服务器就会和数据库服务器创建上万次连接,关闭上万次连接。而数据库创建连接非常消耗时间,关闭连接也消耗时间,严重的浪费数据库的资源,并且极易造成数据库服务器内存溢出、宕机。
在数据库连接池是负责创建,分配,释放数据库连接的对象,在项目启动时会创建一定数量的数据库连接放到连接池对象中,并允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
在 Sequelize
底层: 连接池是一个由 ConnectionManager
类管理的 Pool
类的对象,通过 Pool
类对象来管理和共享多个连接对象。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立,断开都有连接池自身来管理。
// src\modules\BaseDao.ts
this.sequelize = new Sequelize(database, user, password, {
host,
port,
dialect, // 表示是何种数据库
define: {
timestamps: false, // true 表示给模型加上时间戳属性 (createAt,updateAt),false 标识不带时间戳属性
freezeTableName: true, // true 标识使用给定的表名, false 标识模型后名加s作为表名
},
// 数据库连接池
pool: {
// 最大连接对象的个数
max: 10,
// 最小连接数
min: 5,
// 连接池中空闲连接的最大空闲时间,单位为毫秒
idle: 10000,
// 表示一条sql查询在获取连接资源之前的最长等待时间,单位为毫秒
acquire: 1000,
},
});
dao
类中的多个方法才能完成时。dao
中的方法才能完成时dao
类取出来的数据进行处理时CREATE TABLE `dangdang` . `firstctgy` (
`firstctgyId` int NOT NULL AUTO_INCREMENT,
`name` varchar(20) NULL,
PRIMARY KEY ( `firstctgyId` ));
insert into dangdang.firstctgy values(1,'童书'),(2,'电子书'),(3,'女装'),(4,'食品'),(5,'男装'),(6,'数码相机'),(7,'创意文具'),(8,'童装童鞋');
二级分类需要添加外键
CREATE TABLE `dangdang` . `secondctgy`(
`secondctgyId` int NOT NULL AUTO_INCREMENT,
`secctgyName` varchar(20) NOT NULL,
`firstctgyId` int NOT NULL,
PRIMARY KEY (`secondctgyId`),
CONSTRAINT `fk_firstctgyId` FOREIGN KEY(`firstctgyId`) REFERENCES firstctgy(`firstctgyId`));
insert into dangdang.secondctgy values(1,'0-2岁',1);
insert into dangdang.secondctgy values(2,'3-6岁',1);
insert into dangdang.secondctgy values(3,'7-10岁',1);
insert into dangdang.secondctgy values(4,'11-14岁',1);
insert into dangdang.secondctgy values(5,'文艺',2);
insert into dangdang.secondctgy values(6,'人文社科',2);
insert into dangdang.secondctgy values(7,'教育',2);
三级分类需要添加外键
CREATE TABLE `dangdang` . `thirdctgy`(
`thirdctgyId` int NOT NULL AUTO_INCREMENT,
`thirdctgyName` varchar(20) NOT NULL,
`secondctgyId` int NULL,
PRIMARY KEY (`thirdctgyId`),
CONSTRAINT `fk_secondctgyId` FOREIGN KEY(`secondctgyId`) REFERENCES secondctgy(`secondctgyId`));
/*三级分类[二级分类为0-2岁]*/
insert into thirdctgy values(1,'图画故事',1),(2,'认知',1),(3,'益智游戏',1),(4,'纸板书',1),(5,'艺术课堂',1),(6,'入园准备',1);
/*三级分类[二级分类为3-6岁]*/
insert into thirdctgy values(7,'绘画',2),(8,'科普百科',2),(9,'少儿英语',2),(10,'乐高学习',2),(11,'入学准备',2);
/*三级分类[二级分类为7-10岁]*/
insert into thirdctgy(thirdctgyName,secondctgyId) values('文学',3),('科普百科',3),('卡通动漫',3),('童话',3),('少儿英语',3);
/*三级分类[二级分类为11-14岁]*/
insert into thirdctgy(thirdctgyName,secondctgyId) values('励志',4),('地理',4),('政治',4),('趣味默',4),('少儿英语',4),('益智游戏',4),('艺术课堂',4),('游戏/手工',4),('绘画',4);
/*三级分类[二级分类为文艺]*/
insert into thirdctgy(thirdctgyName,secondctgyId) values('小说',5),('哲理文学',5),('传记',5),('青春文学',5),('动漫/幽默',5),('艺术',5),('古籍',5),('法律',5),('经济',5);
/*三级分类[二级分类为人文社科]*/
insert into thirdctgy(thirdctgyName,secondctgyId) values('宗教哲学',6),('历史',6),('传记',6),('教育',6),('社会科学',6),('艺术',6),('工具书',6),('教师用书',6),('考研',6),('公务员',6);
select * from 表A,表B where 表A.主键id=表B.外键id
select * from 表A inner join 表B on 表A.主键id=表B.外键id
select * from 表A left outer join 表B on 表A.主键id=表B.外键id
根据一级分类 Id
,查询所有的二三级分类
async findCtgys(firstctgyId: string) {
const sql = `select * from secondctgy sc inner join thirdctgy tc on sc.secondctgyId = tc.secondctgyId where sc.firstctgyId=${firstctgyId}`;
const [result] = await sequelize.query(sql);
return result;
}
查询出来的结果跟实际前端想要的结果相差甚远,需要进行二次处理。