Стартовый учебник для начинающих или изучение внешнего интерфейса node.js в качестве фона
содержание:
- Сборка и настройка проекта
- Front-end и back-end запросы
header
Основные инструкции по настройке - Запись интерфейса и обработка параметров
- загрузить изображение
- Связать базу данных и интерфейсные операции
- Войти Зарегистрироваться Пользовательский модуль
-
jwt-token
Использование аутентификации (здесь я использую модуль, который написал сам) - Добавить, удалить, изменить и проверить функцию
- Строительство проекта, доставка до линии
Здесь я используюtypescript
Причина его написания — очень полезная подсказка типов и трассировка кода, поэтому в чистом видеjavascript
В проектах по программированиюtypescript
лучше всего поддерживается и читается, используется здесьvscode
этот редактор кода
Кодовый адрес:node-koa
Давайте сначала посмотрим на структуру каталогов
public
В шаблоне хранится статическая страница api-xxx.html, такая как страница отладки внешнего интерфейса.
user.json Временная таблица для хранения данных jwt-токена
upload Временный каталог для хранения загруженных файлов
src
API Каждый интерфейсный модуль также является каталогом маршрутизации.
модули Некоторый каталог функций класса
каталог инструментов utils
Сборка и настройка проекта
1. cd project
и создатьsrc
содержание
mkdir src
2. Инициализироватьpackage.json
, в него будут записываться все последующие конфигурации и команды
npm init
3. Установкаkoa
и соответствующий маршрутkoa-router
npm install koa koa-router
4. УстановкаTypeScript
В соответствии с обнаруженным типом наконечников
npm install --save-dev @types/koa @types/koa-router
5. Тогда естьTypeScript
Подборка горячих обновлений
npm install --save-dev typescript ts-node nodemon
Здесь будет яма (здесь используется оконная среда).Если установка не удалась или не может быть выполнена после установки, онаts-node
иnodemon
Эти две команды должны быть установлены глобально для выполнения горячего обновления: вот так
npm install -g -force ts-node nodemon
6. Настройте сноваpackage.json
настраивать
"scripts": {
"start": "tsc && node dist/index.js",
"serve": "nodemon --watch src/**/* -e ts,tsx --exec ts-node ./src/index.ts"
},
Если это не работаетnpm run serve
Затем скопируйте его вручнуюnodemon --watch src/**/* -e ts,tsx --exec ts-node ./src/index.ts
Не уверен еслиwindow
Проблема в окружающей среде по-прежнемуnpm
Проблема в том, что при первом создании и выполнении проекта все зависимости могут быть установлены локально иnpm run serve
Тоже может быть выполнено отлично, но при повторном открытии проекта возникает ошибка, и причина пока не найдена, но вышеописанными способами можно решить
7. Дополнительное промежуточное ПОkoa-body
Промежуточное ПО используется для анализа параметров POST и загрузки изображений.
npm install koa-body
8. Установите модули базы данных и соответствующие зависимости типов
npm install mysql
npm install --save-dev @types/mysql
9. Наконец, настройте параметры кода
modules/config.ts
Настройки проекта
class ModuleConfig {
/** 端口号 */
readonly port = 1995;
/** 数据库配置 */
readonly db = {
host: "localhost",
user: "root",
password: "root",
/** 数据库名 */
database: "node_ts", // 待会创建数据库的时候就是这个名字
/** 链接上限次数 */
connection_limit: 10
}
/** 接口前缀 */
readonly api_prefix = "/api/v1/";
/** 上传图片存放目录 */
readonly upload_path = "public/upload/images/";
/** 上传图片大小限制 */
readonly upload_img_size = 5 * 1024 * 1024;
/**
* 前端上传图片时约定的字段
* @example
* const formData = new FormData()
* formData.append("img", file)
* XHR.send(formData)
*/
readonly upload_img_name = "img";
/** 用户临时表 */
readonly user_file = "public/user.json";
/** token 长度 */
readonly token_size = 28;
/** token 格式错误提示文字 */
readonly token_tip = "无效的token";
}
/** 项目配置 */
const config = new ModuleConfig();
export default config;
Front-end и back-end запросыheader
Основные инструкции по настройке
index.ts
Под файлом зависимы некоторые интерфейсыtoken
, так что я поставилtoken
Код судебного решения также написан здесь
jwt-token
Я объясню модуль позже, сначала напишите что-нибудь, не приносяtoken
обработка интерфейса
import * as Koa from "koa"; // learn: https://www.npmjs.com/package/koa
import * as koaBody from "koa-body"; // learn: http://www.ptbird.cn/koa-body.html
import config from "./modules/Config";
import router from "./api/main";
import "./api/apiUser"; // 用户模块
import "./api/apiUpload"; // 上传文件模块
import "./api/apiTest"; // 基础测试模块
import "./api/apiTodo"; // 用户列表模块
import { TheContext } from "./utils/interfaces";
const App = new Koa();
// 先统一设置请求配置 => 跨域,请求头信息...
App.use(async (ctx: TheContext, next) => {
// /** 请求路径 */
// const path = ctx.request.path;
console.log("--------------------------");
console.count("request count");
ctx.set({
"Access-Control-Allow-Origin": "*", // 指定请求域,* 就是所有域名都可访问,即跨域打开
// "Content-Type": "application/json",
// "Access-Control-Allow-Credentials": "true",
// "Access-Control-Allow-Methods": "OPTIONS, GET, PUT, POST, DELETE",
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization",
// "X-Powered-By": "3.2.1",
// "Content-Security-Policy": `script-src "self"` // 只允许页面`script`引入自身域名的地址
});
// const hasPath = router.stack.some(item => item.path == path);
// // 判断是否 404
// if (path != "/" && !hasPath) {
// return ctx.body = "<h1 style="text-align: center; line-height: 40px; font-size: 24px; color: tomato">404:访问的页面(路径)不存在</h1>";
// }
// 如果前端设置了 XHR.setRequestHeader("Content-Type", "application/json")
// ctx.set 就必须携带 "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization"
// 如果前端设置了 XHR.setRequestHeader("Authorization", "xxxx") 那对应的字段就是 Authorization
// 并且这里要转换一下状态码
// console.log(ctx.request.method);
if (ctx.request.method === "OPTIONS") {
ctx.response.status = 200;
}
try {
await next();
} catch (err) {
ctx.response.status = err.statusCode || err.status || 500;
ctx.response.body = {
message: err.message
}
}
});
// 使用中间件处理 post 传参 和上传图片
App.use(koaBody({
multipart: true,
formidable: {
maxFileSize: config.uploadImgSize
}
}));
// 开始使用路由
App.use(router.routes())
// 默认无路由模式
// App.use((ctx, next) => {
// ctx.body = html;
// // console.log(ctx.response);
// });
App.on("error", (err, ctx) => {
console.error("server error !!!!!!!!!!!!!", err, ctx);
})
App.listen(config.port, () => {
console.log(`server is running at http://localhost:${ config.port }`);
})
// 参考项目配置连接: https://juejin.im/post/5ce25993f265da1baa1e464f
// mysql learn: https://www.jianshu.com/p/d54e055db5e0
Запустите проект, когда закончите(горячее обновление кода)
npm run watch-update
Запись интерфейса и обработка параметров
Сначала определите маршрут, а затем экспортируйте его для использования.Позже может быть несколько интерфейсов модуля, поэтому все они используются на основе этого.index.ts
Слишком
api/main.ts
под файлом
import * as Router from 'koa-router';
/** api路由模块 */
const router = new Router();
export default router;
api/apiTest.ts
файл, чтобы написать файл, который не нужно подключать к базе данныхGET
иPOST
Запрос используется в качестве теста, и параметры получены.После его написания делается фронтальный запрос.Я не буду объяснять код фронтенда, но вы можете понять его, прочитав комментарии. После написания перейдите на главную страницу, чтобы узнать, правильно ли она работает.
import * as fs from "fs";
import * as path from "path";
import router from "./main";
import utils from "../utils";
import { apiSuccess } from "../utils/apiResult";
import config from "../modules/Config";
/** 资源路径 */
const resourcePath = path.resolve(__dirname, '../../public/template');
const template = fs.readFileSync(resourcePath + "/page.html", "utf-8");
// "/*" 监听全部
router.get("/", (ctx, next) => {
// 指定返回类型
// ctx.response.type = "html";
ctx.response.type = "text/html; charset=utf-8";
const data = {
pageTitle: "serve-root",
jsLabel: "",
content: `<button class="button button_green"><a href="/home">go to home<a></button>`
}
ctx.body = utils.replaceText(template, data);
// console.log("根目录");
// 路由重定向
// ctx.redirect("/home");
// 302 重定向到其他网站
// ctx.status = 302;
// ctx.redirect("https://www.baidu.com");
})
router.get("/home", (ctx, next) => {
ctx.response.type = "text/html; charset=utf-8";
const data = {
pageTitle: "serve-root",
jsLabel: "",
content: `<h1 style="text-align: center; line-height: 40px; font-size: 24px; color: #007fff">Welcome to home</h1>`
}
ctx.body = utils.replaceText(template, data);
// console.log("/home");
})
// get 请求
router.get("/getData", (ctx, next) => {
/** 接收参数 */
const params: object | string = ctx.query || ctx.querystring;
console.log("/getData", params);
ctx.body = apiSuccess({
method: "get",
port: config.port,
date: utils.formatDate()
});
})
// post 请求
router.post("/postData", (ctx, next) => {
/** 接收参数 */
const params: object = ctx.request.body || ctx.params;
console.log("/postData", params);
const result = {
data: "请求成功"
}
ctx.body = apiSuccess(result, "post success")
})
загрузить изображение
загрузить на сервер
modules/api/apiUpload.ts
под файлом,
import * as fs from "fs";
import * as path from "path";
import router from "./main";
import config from "../modules/Config";
import { UploadFile } from "../utils/interfaces";
import { apiSuccess } from "../utils/apiResult";
// 上传图片
// learn: https://www.cnblogs.com/nicederen/p/10758000.html
// learn: https://blog.csdn.net/qq_24134853/article/details/81745104
router.post("/uploadImg", async (ctx, next) => {
const file: UploadFile = ctx.request.files[config.uploadImgName] as any;
let fileName: string = ctx.request.body.name || `img_${Date.now()}`;
fileName = `${fileName}.${file.name.split(".")[1]}`;
// 创建可读流
const render = fs.createReadStream(file.path);
const filePath = path.join(config.uploadPath, fileName);
const fileDir = path.join(config.uploadPath);
if (!fs.existsSync(fileDir)) {
fs.mkdirSync(fileDir);
}
// 创建写入流
const upStream = fs.createWriteStream(filePath);
render.pipe(upStream);
// console.log(fileName, file);
const result = {
image: `http://${ctx.headers.host}/${config.uploadPath}${fileName}`,
file: `${config.uploadPath}${fileName}`
}
ctx.body = apiSuccess(result, "上传成功");
})
Загрузить в облако Alibaba
Сначала установите соответствующий SDK, просто прочитайте документацию здесь, код очень простой
# 模块依赖
npm install ali-oss -S
# 类型包
npm install @types/ali-oss -D
сегмент кода
import * as path from "path";
import * as OSS from "ali-oss";
import router from "./main";
import config from "../modules/Config";
import { TheContext, UploadFile } from "../utils/interfaces";
import { apiSuccess } from "../utils/apiResult";
/**
* - [阿里云 OSS-API 文档](https://help.aliyun.com/document_detail/32068.html?spm=a2c4g.11186623.6.1074.612626fdu6LBB7)
* - [用户管理](https://ram.console.aliyun.com/users)
*/
const client = new OSS({
// yourregion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
region: "oss-cn-guangzhou",
accessKeyId: "阿里云平台生成的key",
accessKeySecret: "阿里云平后台生成的secret",
bucket: "指定上传的 bucket 名",
});
// 上传图片
// learn: https://www.cnblogs.com/nicederen/p/10758000.html
// learn: https://blog.csdn.net/qq_24134853/article/details/81745104
router.post("/uploadImg", async ctx => {
const file: UploadFile = ctx.request.files[config.uploadImgName] as any;
// console.log("file >>", file);
let fileName: string = `${Math.random().toString(36).slice(2)}-${Date.now()}-${file.name}`;
const result = await client.put(`images/${fileName}`, path.normalize(file.path));
// console.log("上传文件结果 >>", result);
ctx.body = apiSuccess(result, "上传成功");
})
jwt-token
Использование (здесь я использую модуль, написанный мной)
Идея реализации: использоватьjs
память для чтения и записи пользовательскихtoken
информация, только при записи в память (асинхронная запись предотвращает блокировку)json
формат для записиuser.json
файл, а затем прочитать последнюю запись при создании экземпляраtoken
информацию и отбрасывать устаревшие. Делайте выводы друг о друге, вы можете использовать его при написании или чтенииRedis
способ вместо чтения и записи локальногоuser.json
Таблица, принцип тот же.
Процесс реализации:
-
существует
public/
Создайте новый в каталогеuser.json
файл как временную таблицу записей токенов -
затем определите
ModuleJWT
модуль,userRecord
Эта частная собственность являетсяtoken
заkey
Объект для хранения информации о пользователе, эта информация имеет параметрonline
Это означает онлайн-время, которое будет использоваться при оценке значения позже. -
ModuleJWT
В модуле всего 3 метода, выставленных во внешний мир:-
setRecord
Установите запись в первый раз и вернитесь к входу по токену, и запишите вuserRecord
Внутри, а потом пишем во временную таблицуuser.json
. -
updateRecord
Это метод, который будет выполняться первым каждый раз, когда делается запрос для оценки передачи внешнего интерфейса.token
Вы вuserRecord
В ней, если она есть, то судиuserRecord[token].online
и другие операции, все вынесенные решения указывают текущийtoken
нет проблем и обновитьuserRecord[token].online
Текущее время и, наконец, запись во временную таблицу. Этот процесс может быть более сложным, но лучше посмотреть на код, чтобы понять его. -
removeRecord
Эта логика проста, прямо изuserRecord
удалить текущий токен в
-
-
ModuleJWT
При создании экземпляра начните сuser.json
Прочитайте последнюю записанную информацию во временной таблице, а затем удалите устаревшиеtoken
, чтобы обеспечить синхронизацию данных. -
Наконец, в процессе выполнения кода регулярно удаляйте устаревшие токены, чтобы снизить производительность чтения и записи и размер памяти, это зависит от кода.
modules/Jwt.ts
под файлом
import * as fs from "fs";
import config from "./Config";
import { apiSuccess } from "../utils/apiResult";
import {
UserRecordType,
UserInfoType,
JwtResultType,
TheContext,
ApiResult
} from "../utils/interfaces";
/**
* 自定义`jwt-token`验证模块,区别于[koa-jwt](https://www.npmjs.com/package/koa-jwt)
* @author [Hjs](https://github.com/Hansen-hjs)
*/
class ModuleJWT {
constructor() {
this.init();
}
/** 效期(小时) */
private maxAge = 12;
/** 更新 & 检测时间间隔(10分钟) */
private interval = 600000;
/** 用户`token`纪录 */
private userRecord: UserRecordType = {};
/**
* 写入文件
* @param obj 要写入的对象
*/
private write(obj?: UserRecordType) {
const data = obj || this.userRecord;
// 同步写入(貌似没必要)
// fs.writeFileSync(config.userFile, JSON.stringify(data), { encoding: "utf8" });
// 异步写入
fs.writeFile(config.userFile, JSON.stringify(data), { encoding: "utf8" }, err => {
if (err) {
console.log(`\x1B[41m jwt-token 写入失败 \x1B[0m`, err);
} else {
console.log(`\x1B[42m jwt-token 写入成功 \x1B[0m`);
}
})
}
/** 从本地临时表里面初始化用户状态 */
private init() {
// fs.accessSync(config.userFile)
if (!fs.existsSync(config.userFile)) {
console.log(`\x1B[42m ${config.userFile} 不存在,开始创建该文件 \x1B[0m`);
fs.writeFileSync(config.userFile, "{}", { encoding: "utf8" });
}
const userFrom = fs.readFileSync(config.userFile).toString();
this.userRecord = userFrom ? JSON.parse(userFrom) : {};
this.checkRecord();
// console.log("token临时表", userFrom, this.userRecord);
}
/** 生成`token` */
private getToken() {
const getCode = (n: number): string => {
let codes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789";
let code = "";
for (let i = 0; i < n; i++) {
code += codes.charAt(Math.floor(Math.random() * codes.length));
}
if (this.userRecord[code]) {
return getCode(n);
}
return code;
}
const code = getCode(config.tokenSize);
return code;
}
/** 定时检测过期的`token`并清理 */
private checkRecord() {
const check = () => {
const now = Date.now();
let isChange = false;
for (const key in this.userRecord) {
if (this.userRecord.hasOwnProperty(key)) {
const item = this.userRecord[key];
if (now - item.online > this.maxAge * 3600000) {
isChange = true;
delete this.userRecord[key];
}
}
}
if (isChange) {
this.write();
}
}
// 定时检测
setInterval(check, this.interval);
check();
}
/**
* 设置纪录并返回`token`
* @param data 用户信息
*/
setRecord(data: UserInfoType) {
const token = this.getToken();
data.online = Date.now();
this.userRecord[token] = data;
this.write();
return token;
}
/**
* 更新并检测`token`
* @param token
* @description 这里可以做单点登录的处理,自行修改一下规则判断即可
*/
updateRecord(token: string) {
const result: JwtResultType = {
message: "",
success: false,
info: null
}
if (!this.userRecord.hasOwnProperty(token)) {
result.message = "token 已过期或不存在";
return result;
}
const userInfo = this.userRecord[token];
const now = Date.now();
if (now - userInfo.online > this.maxAge * 3600000) {
result.message = "token 已过期";
return result;
}
result.message = "token 通过验证";
result.success = true;
result.info = userInfo;
// 更新在线时间并写入临时表
// 这里优化一下,写入和更新的时间间隔为10分钟,避免频繁写入
if (now - userInfo.online > this.interval) {
this.userRecord[token].online = now;
this.write();
}
return result;
}
/**
* 从纪录中删除`token`纪录
* @param token
* @description 主要是退出登录时用
*/
removeRecord(token: string) {
if (this.userRecord.hasOwnProperty(token)) {
delete this.userRecord[token];
this.write();
return true;
} else {
return false;
}
}
/**
* 检测需要`token`的接口状态
* @param context
*/
checkToken(context: TheContext) {
const token: string = context.header.authorization;
let fail = false;
let info: ApiResult;
if (!token) {
fail = true;
info = apiSuccess({}, "缺少token", 400);
}
if (token && token.length != config.tokenSize) {
fail = true;
info = apiSuccess({}, config.tokenTip, 400);
}
const state = this.updateRecord(token);
if (!state.success) {
fail = true;
info = apiSuccess({}, state.message, 401);
}
// 设置 token 信息到上下文中给接口模块里面调用
if (!fail) {
context["theState"] = state;
}
return {
fail,
info
}
}
}
/** `jwt-token`模块 */
const jwt = new ModuleJWT();
export default jwt;
Подключение к базе данных и операциям интерфейса
Локальный сервис, который я использую здесь, построен с помощью (upupw), очень простой операции, инструмент таблицы базы данных navicat
адрес загрузки navicatВ сети тоже много взломанного, можете скачать сами
-
использовать здесь
upupw
После получения пароля учетной записи данных в командном окне,navicat
Создайте новое подключение к базе данных, заполните в соответствии с паролем учетной записи, будьте осторожны, чтобы не изменить порт по умолчанию, иначе подключение не будет выполнено. -
После подключения создайте новую базу данных в левой колонке с именем
node_ts
, в кодеconfig.db.database
Вот и все, а таблица будет построена позже в этой колонке.
- начать строить
user_form
Таблица
Если вы не хотите создавать таблицу, вы можете напрямую добавить проектmysql/node_ts.sql
импорт файловnode_ts
база данных может
вернуться к проектуsrc/utils/mysql.ts
Под файлом здесь инкапсулируется метод добавления, удаления, изменения и проверки базы данных, и все последующие операции с базой данных выполняются через этот метод.
import * as mysql from "mysql"; // learn: https://www.npmjs.com/package/mysql
import config from "../modules/Config";
interface queryResult {
/** `state===1`时为成功 */
state: number
/** 结果数组 或 对象 */
results: any
/** 状态 */
fields: Array<mysql.FieldInfo>
/** 错误信息 */
error: mysql.MysqlError
/** 描述信息 */
msg: string
}
/** 数据库 */
const pool = mysql.createPool({
host: config.db.host,
user: config.db.user,
password: config.db.password,
database: config.db.database
});
/**
* 数据库增删改查
* @param command 增删改查语句
* @param value 对应的值
*/
export default function query(command: string, value?: Array<any>) {
const result: queryResult = {
state: 0,
results: null,
fields: null,
error: null,
msg: ""
}
return new Promise<queryResult>((resolve, reject) => {
pool.getConnection((error: any, connection) => {
if (error) {
result.error = error;
result.msg = "数据库连接出错";
resolve(result);
} else {
const callback: mysql.queryCallback = (error: any, results, fields) => {
// pool.end();
connection.release();
if (error) {
result.error = error;
result.msg = "数据库增删改查出错";
resolve(result);
} else {
result.state = 1;
result.msg = "ok";
result.results = results;
result.fields = fields;
resolve(result);
}
}
if (value) {
pool.query(command, value, callback);
} else {
pool.query(command, callback);
}
}
});
});
}
// learn: https://blog.csdn.net/gymaisyl/article/details/84777139
Войти Зарегистрироваться Пользовательский модуль
mysql
оператор запросаuser_form
Табличные поля
src/api/apiUser.ts
под файлом
import router from "./main";
import query from "../utils/mysql";
import jwt from "../modules/Jwt";
import config from "../modules/Config";
import {
UserInfoType,
TheContext,
ApiResult
} from "../utils/interfaces";
import { apiSuccess, apiFail } from "../utils/apiResult";
// 注册
router.post("/register", async (ctx) => {
/** 接收参数 */
const params: UserInfoType = ctx.request.body;
/** 返回结果 */
let bodyResult: ApiResult;
/** 账号是否可用 */
let validAccount = false;
// console.log("注册传参", params);
if (!/^[A-Za-z0-9]+$/.test(params.account)) {
return ctx.body = apiSuccess({}, "注册失败!账号必须由英文或数字组成", 400);
}
if (!/^[A-Za-z0-9]+$/.test(params.password)) {
return ctx.body = apiSuccess({}, "注册失败!密码必须由英文或数字组成", 400);
}
if (!params.name.trim()) {
params.name = "用户未设置昵称";
}
// 先查询是否有重复账号
const res = await query(`select account from user_form where account='${ params.account }'`)
// console.log("注册查询", res);
if (res.state === 1) {
if (res.results.length > 0) {
bodyResult = apiSuccess({}, "该账号已被注册", 400);
} else {
validAccount = true;
}
} else {
ctx.response.status = 500;
bodyResult = apiFail(res.msg, 500, res.error);
}
// 再写入表格
if (validAccount) {
const res = await query("insert into user_form(account, password, username) values(?,?,?)", [params.account, params.password, params.name])
if (res.state === 1) {
bodyResult = apiSuccess(params, "注册成功");
} else {
ctx.response.status = 500;
bodyResult = apiFail(res.msg, 500, res.error);
}
}
ctx.body = bodyResult;
})
// 登录
router.post("/login", async (ctx) => {
/** 接收参数 */
const params: UserInfoType = ctx.request.body;
/** 返回结果 */
let bodyResult: ApiResult;
// console.log("登录", params);
if (!params.account || params.account.trim() === "") {
return ctx.body = apiSuccess({}, "登录失败!账号不能为空", 400);
}
if (!params.password || params.password.trim() === "") {
return ctx.body = apiSuccess({}, "登录失败!密码不能为空", 400);
}
// 先查询是否有当前账号
const res = await query(`select * from user_form where account = "${ params.account }"`)
console.log("登录查询", res);
if (res.state === 1) {
// 再判断账号是否可用
if (res.results.length > 0) {
const data: UserInfoType = res.results[0];
// 最后判断密码是否正确
if (data.password == params.password) {
data.token = jwt.setRecord({
id: data.id,
account: data.account,
password: data.password
});
bodyResult = apiSuccess(data ,"登录成功");
} else {
bodyResult = apiSuccess({}, "密码不正确", 400);
}
} else {
bodyResult = apiSuccess({}, "该账号不存在,请先注册", 400);
}
} else {
ctx.response.status = 500;
bodyResult = apiFail(res.msg, 500, res.error);
}
ctx.body = bodyResult;
})
// 获取用户信息
router.get("/getUserInfo", async (ctx: TheContext) => {
const checkInfo = jwt.checkToken(ctx);
if (checkInfo.fail) {
return ctx.body = checkInfo.info;
}
const state = ctx["theState"];
// /** 接收参数 */
// const params = ctx.request.body;
/** 返回结果 */
let bodyResult: ApiResult;
// console.log("getUserInfo", params, state);
const res = await query(`select * from user_form where account = "${ state.info.account }"`)
if (res.state === 1) {
// 判断账号是否可用
if (res.results.length > 0) {
const data: UserInfoType = res.results[0];
bodyResult = apiSuccess(data);
} else {
bodyResult = apiSuccess({}, "该账号不存在,可能已经从数据库中删除", 400);
}
} else {
ctx.response.status = 500;
bodyResult = apiFail(res.msg, 500, res.error);
}
ctx.body = bodyResult;
})
// 退出登录
router.get("/logout", ctx => {
const checkInfo = jwt.checkToken(ctx);
if (checkInfo.fail) {
return ctx.body = checkInfo.info;
}
const token: string = ctx.header.authorization;
/** 接收参数 */
const params = ctx.request.body;
console.log("logout", params, token);
if (token.length != config.tokenSize) {
return ctx.body = apiSuccess({}, config.tokenTip);
}
const state = jwt.removeRecord(token);
if (state) {
return ctx.body = apiSuccess({}, "退出登录成功");
} else {
return ctx.body = apiSuccess({}, "token 不存在", 400);
}
})
Добавить, удалить, изменить и проверить функцию
Затем создайте еще один с именемtodo_form
Таблица
Наконец создайте новыйapiTodo.ts
Интерфейсный модуль для добавления, удаления, изменения и проверки списка пользователей
import router from './main';
import query from '../modules/mysql';
import stateInfo from '../modules/state';
import { mysqlQueryType, mysqlErrorType, JwtResultType } from '../modules/interfaces';
// 获取所有列表
router.get('/getList', async (ctx) => {
const checkInfo = jwt.checkToken(ctx);
if (checkInfo.fail) {
return ctx.body = checkInfo.info;
}
const state: JwtResultType = ctx['theState'];
/** 返回结果 */
let bodyResult = null;
// console.log('getList');
// 这里要开始连表查询
await query(`select * from todo_form where user_id = '${ state.info.id }'`).then((res: mysqlQueryType) => {
// console.log('/getList 查询', res.results);
bodyResult = stateInfo.getSuccessData({
list: res.results.length > 0 ? res.results : []
});
}).catch((err: mysqlErrorType) => {
bodyResult = stateInfo.getFailData(err.message);
})
ctx.body = bodyResult;
})
// 添加列表
router.post('/addList', async (ctx) => {
const checkInfo = jwt.checkToken(ctx);
if (checkInfo.fail) {
return ctx.body = checkInfo.info;
}
const state: JwtResultType = ctx['theState'];
/** 接收参数 */
const params = ctx.request.body;
/** 返回结果 */
let bodyResult = null;
if (!params.content) {
return ctx.body = stateInfo.getFailData('添加的列表内容不能为空!');
}
// 写入列表
await query('insert into todo_form(content, time, user_id) values(?,?,?)', [params.content, new Date().toLocaleDateString(), state.info.id]).then((res: mysqlQueryType) => {
// console.log('写入列表', res.results.insertId);
bodyResult = stateInfo.getSuccessData({
id: res.results.insertId
}, '添加成功');
}).catch((err: mysqlErrorType) => {
// console.log('注册写入错误', err);
bodyResult = stateInfo.getFailData(err.message);
})
ctx.body = bodyResult;
})
// 修改列表
router.post('/modifyList', async (ctx) => {
const checkInfo = jwt.checkToken(ctx);
if (checkInfo.fail) {
return ctx.body = checkInfo.info;
}
const state: JwtResultType = ctx['theState'];
/** 接收参数 */
const params = ctx.request.body;
/** 返回结果 */
let bodyResult = null;
if (!params.id) {
return ctx.body = stateInfo.getFailData('列表id不能为空');
}
if (!params.content) {
return ctx.body = stateInfo.getFailData('列表内容不能为空');
}
// 修改列表
await query(`update todo_form set content='${params.content}', time='${new Date().toLocaleDateString()}' where list_id='${params.id}'`).then((res: mysqlQueryType) => {
console.log('修改列表', res);
if (res.results.affectedRows > 0) {
bodyResult = stateInfo.getSuccessData({}, '修改成功');
} else {
bodyResult = stateInfo.getFailData('列表id不存在');
}
}).catch((err: mysqlErrorType) => {
// console.log('注册写入错误', err);
bodyResult = stateInfo.getFailData(err.message);
})
ctx.body = bodyResult;
})
// 删除列表
router.post('/deleteList', async (ctx) => {
const checkInfo = jwt.checkToken(ctx);
if (checkInfo.fail) {
return ctx.body = checkInfo.info;
}
const state: JwtResultType = ctx['theState'];
/** 接收参数 */
const params = ctx.request.body;
/** 返回结果 */
let bodyResult = null;
// 从数据库中删除
await query(`delete from todo_form where list_id=${params.id} and user_id = ${state.info.id}`).then((res: mysqlQueryType) => {
console.log('从数据库中删除', res);
if (res.results.affectedRows > 0) {
bodyResult = stateInfo.getSuccessData({}, '删除成功');
} else {
bodyResult = stateInfo.getFailData('当前列表id不存在或已删除');
}
}).catch((err: mysqlErrorType) => {
console.log('从数据库中删除失败', err);
bodyResult = stateInfo.getFailData(err.message);
})
ctx.body = bodyResult;
})
Сторонний интерфейс внутреннего запроса (расширенный)
- это
node.js
Одна из основных функций, в принципе иKoa
Это не имеет к этому никакого отношения, потому что актуальной информации, которую можно найти в Интернете, относительно немного, поэтому я кстати тоже привожу ее сюда. - Эта функция в основном используется, когда нам нужно получить некоторые сторонние данные, а затем вернуть их во внешний интерфейс, аналогично внутреннему интерфейсу апплета WeChat.
utils
Создайте новый в каталогеrequest.ts
import * as http from "http";
import * as querystring from "querystring"
import * as zlib from "zlib"
import { ServeRequestResult } from "./interfaces";
/**
* 服务端请求
* - [基础请求参考](https://www.cnblogs.com/liAnran/p/9799296.html)
* - [响应结果乱码参考](https://blog.csdn.net/fengxiaoxiao_1/article/details/72629577)
* - [html乱码参考](https://www.microanswer.cn/blog/51)
* - [node-http文档](http://nodejs.cn/api/http.html#http_class_http_incomingmessage)
* @param options 请求配置
* @param params 请求传参数据
*/
export default function request(options: http.RequestOptions, params: object = {}): Promise<ServeRequestResult> {
/** 返回结果 */
const info: ServeRequestResult = {
msg: "",
result: "",
state: -1
}
/** 传参字段 */
const data = querystring.stringify(params as any);
if (data && options.method == "GET") {
options.path += `?${data}`;
}
return new Promise((resolve, reject) => {
const clientRequest = http.request(options, res => {
// console.log("http.get >>", res);
console.log(`http.request.statusCode: ${res.statusCode}`);
console.log(`http.request.headers: ${JSON.stringify(res.headers)}`);
// 因为现在自己解码,所以就不设置编码了。
// res.setEncoding("utf-8");
if (res.statusCode !== 200) {
info.msg = "请求失败";
info.result = {
statusCode: res.statusCode,
headers: res.headers
}
return resolve(info);
}
let output: http.IncomingMessage | zlib.Gunzip
if (res.headers["content-encoding"] == "gzip") {
const gzip = zlib.createGunzip();
res.pipe(gzip);
output = gzip;
} else {
output = res;
}
output.on("data", function(chunk) {
console.log("----------> chunk >>");
// info.result += chunk;
// info.result = chunk;
info.result += chunk.toString();
});
output.on("error", function(error) {
console.log("----------> 服务端请求错误 >>", error);
info.msg = error.message;
info.result = error;
})
output.on("end", function() {
console.log("---------- end ----------");
if (res.complete) {
info.msg = "ok";
info.state = 1;
resolve(info);
} else {
info.msg = "连接中断"
resolve(info);
}
});
})
if (data && options.method != "GET") {
clientRequest.write(data)
}
clientRequest.end()
})
}
Пример использованияИнтерфейс прогноза погоды здесь используется как демонстрация, если вы хотите запросить какие-то уникальные интерфейсы, вы можете настроить его самостоятельно, если хотите собирать данные.headers
Просто смоделируйте
import { apiSuccess, apiFail } from "../utils/apiResult";
import request from "../utils/request";
// 请求第三方接口并把数据返回到前端
router.get("/getWeather", async (ctx, next) => {
console.log("ctx.query >>", ctx.query);
if (!ctx.query.city) {
ctx.body = apiSuccess({}, "缺少传参字段 city", 400);
return;
}
const res = await request({
method: "GET",
hostname: "wthrcdn.etouch.cn",
path: "/weather_mini?city=" + encodeURIComponent(ctx.query.city),
// headers: {
// "xxx": "asfdfewf"
// }
})
// console.log("获取天气信息 >>", res);
if (res.state === 1) {
if (utils.checkType(res.result) === "string") {
res.result = JSON.parse(res.result);
}
ctx.body = apiSuccess(res.result)
} else {
ctx.body = apiFail(res.msg, 500, res.result)
}
})
Сборки проекта передаются в онлайн
- Купитьоблачный сервер ECSTencent Ali бесплатен;
- установить соответствующий
node.js
,myql
,git
и другие инструменты, которые по сути такие же, как те, которые устанавливаются на новый компьютер, за исключением того, что серверLinux
система, которая требует помощиMobaXtermЭтот инструмент используется для удаленного подключения, а затем установки и выполнения других операций, я не буду его здесь раскрывать, да и писать код не нужно... - Создайте каталог, а затем используйте
git
Потяните код вниз,npm install
Затем запуститеnpm run start
Все так, но обнаруживается, что служба закрывается после выхода, поэтому надо установить другое управление процессами.pm2заменить наше руководствоnpm run start
; - установлен
pm2
Затем вам нужно написать файл в текущем каталоге проектаpm2.json
файл запуска конфигурации, что-то вродеpackage.json
Опять же, фрагмент кода выглядит следующим образом:
{
"name": "node-serve", // 进程的名字
"script": "npm run start" // 运行代码的命令
}
окончательное исполнениеpm2 start pm2.json
Команда запускает проект. Чтобы просмотреть и другие операции, обратитесь к следующим командам
# 启动任务
pm2 start pm2.json
# 强制停止所有的服务
pm2 kill
# 查看服务
pm2 list
# 查看指定进程日志,0是任务列表索引值
pm2 log 0
# 重启指定进程,0是任务列表索引值
pm2 restart 0
# 停止指定进程,0是任务列表索引值
pm2 stop 0
Последнее примечание
Вся фронтенд-отладка проекта пишется, а код фронтенд-интерфейса хранится вpublic/template
Далее, поскольку это совместное использование внутреннего кода, здесь нет демонстрации внешнего кода.