feat: add

This commit is contained in:
xiaoxian521 2021-11-22 12:30:58 +08:00
parent f7c6bf5d46
commit 15fd86ba73
31 changed files with 878 additions and 0 deletions

8
.env Normal file
View File

@ -0,0 +1,8 @@
# Port
PORT=3000
# JWT_SECRET
JWT_SECRET = '708DD1DC5BC5A169'
# Debug
LOG_LEVEL='debug'

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
.DS_Store
dist
yarn.lock

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# 接口
## 安装依赖
```
yarn install
```
## 项目启动
```
yarn dev
```
## Swagger文档访问地址
http://localhost:3000
## 在swagger中添加token验证
① 先请求验证码接口拿到验证码info字段就是验证码
② 然后请求登录接口你可以在网页的Network中拿到登录成功后返回的token复制
③ 最后回到swagger点击右上角的绿色边框Authorize你会看到一个Value的输入框将复制的token前面加上Bearer 粘贴上去点确定即可Authorize
注意Bearer后面有一个空格哦
## 注意点
请先全局安装typescript、ts-node如安装请忽略
```
npm install -g typescript
npm install -g ts-node
```

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "backend-ts",
"version": "1.0.0",
"description": "API接口",
"main": "index.js",
"scripts": {
"build": "tsc",
"dev": "ts-node ./src/server.ts",
"start": "nodemon ./dist/server.js",
"prod": "npm run build && npm run start",
"yarn:clean": "yarn cache clean"
},
"author": "xiaoxian521",
"license": "ISC",
"dependencies": {
"@types/express": "^4.17.9",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-swagger-generator": "^1.1.17",
"jsonwebtoken": "^8.5.1",
"mysql2": "^2.2.5",
"svg-captcha": "^1.4.0",
"winston": "^3.3.3"
},
"devDependencies": {
"body-parser": "^1.19.0",
"nodemon": "^1.19.4",
"open": "^7.3.0",
"tslint": "^5.20.1",
"typescript": "^3.9.7"
},
"repository": "git@github.com:xiaoxian521/vue-pure-admin.git"
}

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

59
public/index.html Normal file
View File

@ -0,0 +1,59 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css">
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script>
window.onload = function () {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "http://localhost:3000/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>

View File

@ -0,0 +1,74 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
window.addEventListener('DOMContentLoaded', function () {
run();
});
</script>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
public/swagger-ui.css Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
public/swagger-ui.js Normal file

File diff suppressed because one or more lines are too long

1
public/swagger-ui.js.map Normal file

File diff suppressed because one or more lines are too long

31
src/app.ts Normal file
View File

@ -0,0 +1,31 @@
import * as express from "express"
import * as bodyParser from "body-parser"
class App {
public app: express.Application
constructor() {
this.app = express()
this.config()
}
private config(): void {
// 支持json编码的主体
this.app.use(bodyParser.json())
// 支持编码的主体
this.app.use(bodyParser.urlencoded({
extended: true,
}))
// 设置静态访问目录(Swagger)
this.app.use(express.static('public'))
// 设置跨域访问
this.app.all('*', (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'content-type')
res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS')
res.header('X-Powered-By', ' 3.2.1')
res.header('Content-Type', 'application/json;charset=utf-8')
next()
})
}
}
export default new App().app

66
src/config/index.ts Normal file
View File

@ -0,0 +1,66 @@
import * as dotenv from "dotenv"
process.env.NODE_ENV = process.env.NODE_ENV || "development"
const envFound = dotenv.config()
if (envFound.error) {
throw new Error("⚠️ Couldn't find .env file ⚠️")
}
export default {
port: parseInt(process.env.PORT, 10),
databaseURL: process.env.MONGODB_URI,
jwtSecret: process.env.JWT_SECRET,
jwtAlgorithm: process.env.JWT_ALGO,
options: {
swaggerDefinition: {
info: {
description: 'CURD-TS专用接口',
title: 'Swagger',
version: require('../../package.json').version
},
host: `localhost:${parseInt(process.env.PORT, 10)}`,
basePath: '/',
produces: ['application/json', 'application/xml'],
schemes: ['http', 'https'],
securityDefinitions: {
JWT: {
type: 'apiKey',
in: 'header',
name: 'Authorization',
description: 'Bearer Authorization'
}
}
},
route: {
url: './swagger-ui.html',
docs: '/swagger.json' //swagger文件 api
},
basedir: __dirname, //app absolute path
files: ['../router/api/*.ts'] //Path to the API handle folder
},
logs: {
level: process.env.LOG_LEVEL || 'silly',
},
agenda: {
dbCollection: process.env.AGENDA_DB_COLLECTION,
pooltime: process.env.AGENDA_POOL_TIME,
concurrency: parseInt(process.env.AGENDA_CONCURRENCY, 10),
},
mysql: {
host: 'localhost',
charset: 'utf8_general_ci',
user: 'root',
password: '123456789'
},
mongodb: {},
sqlite: {},
api: {
prefix: '/api',
},
emails: {
apiKey: process.env.MAILGUN_API_KEY,
domain: process.env.MAILGUN_DOMAIN
}
}

35
src/loaders/logger.ts Normal file
View File

@ -0,0 +1,35 @@
import config from "../config"
import * as winston from "winston"
const transports = []
if (process.env.NODE_ENV !== 'development') {
transports.push(
new winston.transports.Console()
)
} else {
transports.push(
new winston.transports.Console({
format: winston.format.combine(
winston.format.cli(),
winston.format.splat(),
)
})
)
}
const LoggerInstance = winston.createLogger({
level: config.logs.level,
levels: winston.config.npm.levels,
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
),
transports
})
export default LoggerInstance

View File

@ -0,0 +1,6 @@
// 创建用户表
const user = 'CREATE TABLE if not EXISTS users(id int PRIMARY key auto_increment,username varchar(32),password varchar(32),time DATETIME)'
export {
user
}

347
src/router/api/mysql.ts Normal file
View File

@ -0,0 +1,347 @@
import * as mysql from "mysql2"
import secret from "../../config"
import * as jwt from "jsonwebtoken"
import { createHash } from "crypto"
import Logger from "../../loaders/logger"
import { Request, Response } from "express"
import { createMathExpr } from "svg-captcha"
import getFormatDate from "../../utils/date"
import { Code, Info } from "../../utils/infoEnum"
import { connection } from "../../utils/initMysql"
export interface dataModel {
length: number
}
// 保存验证码
let generateVerify: number
/**
* @typedef Error
* @property {string} code.required
*/
/**
* @typedef Response
* @property {[integer]} code
*/
/**
* @typedef Login
* @property {string} username.required - - eg: admin
* @property {string} password.required - - eg: 123456
* @property {integer} verify.required -
*/
/**
* @route POST /login
* @param {Login.model} point.body.required - the new point
* @produces application/json application/xml
* @consumes application/json application/xml
* @summary
* @group
* @returns {Response.model} 200
* @returns {Array.<Login>} Login
* @headers {integer} 200.X-Rate-Limit
* @headers {string} 200.X-Expires-After
* @security JWT
*/
const login = async (req: Request, res: Response) => {
const { username, password, verify } = req.body
if (generateVerify !== verify) return res.json({
code: Code.failCode,
info: Info[0]
})
let sql: string = 'select * from users where username=' + "'" + username + "'"
connection.query(sql, async function (err, data: dataModel) {
if (data.length == 0) {
await res.json({
code: Code.failCode,
info: Info[1]
})
} else {
if (createHash('md5').update(password).digest('hex') == data[0].password) {
const accessToken = jwt.sign({
accountId: data[0].id
}, secret.jwtSecret, { expiresIn: 3600 })
await res.json({
code: Code.successCode,
info: Info[2],
accessToken
})
} else {
await res.json({
code: Code.failCode,
info: Info[3]
})
}
}
})
}
/**
* @typedef Register
* @property {string} username.required - - eg: admin
* @property {string} password.required - - eg: 123456
* @property {integer} verify.required -
*/
/**
* @route POST /register
* @param {Register.model} point.body.required - the new point
* @produces application/json application/xml
* @consumes application/json application/xml
* @summary
* @group
* @returns {Response.model} 200
* @returns {Array.<Register>} Register
* @headers {integer} 200.X-Rate-Limit
* @headers {string} 200.X-Expires-After
* @security JWT
*/
const register = async (req: Request, res: Response) => {
const { username, password, verify } = req.body
if (generateVerify !== verify) return res.json({
code: Code.failCode,
info: Info[0]
})
if (password.length < 6) return res.json({
code: Code.failCode,
info: Info[4]
})
let sql: string = 'select * from users where username=' + "'" + username + "'"
connection.query(sql, async (err, data: dataModel) => {
if (data.length > 0) {
await res.json({
code: Code.failCode,
info: Info[5]
})
} else {
let time = await getFormatDate()
let sql: string = 'insert into users (username,password,time) value(' + "'" + username + "'" + ',' + "'" + createHash('md5').update(password).digest('hex') +
"'" + ',' + "'" + time + "'" + ')'
connection.query(sql, async function (err) {
if (err) {
Logger.error(err)
} else {
await res.json({
code: Code.successCode,
info: Info[6]
})
}
})
}
})
}
/**
* @typedef UpdateList
* @property {string} username.required - - eg: admin
*/
/**
* @route PUT /updateList/{id}
* @summary
* @param {UpdateList.model} point.body.required -
* @param {UpdateList.model} id.path.required - id
* @group
* @returns {object} 200
* @returns {Array.<UpdateList>} UpdateList
* @security JWT
*/
const updateList = async (req: Request, res: Response) => {
const { id } = req.params
const { username } = req.body
let payload = null
try {
const authorizationHeader = req.get("Authorization")
const accessToken = authorizationHeader.substr("Bearer ".length)
payload = jwt.verify(accessToken, secret.jwtSecret)
} catch (error) {
return res.status(401).end()
}
let modifySql: string = 'UPDATE users SET username = ? WHERE id = ?'
let sql: string = 'select * from users where id=' + id
connection.query(sql, function (err, data) {
connection.query(sql, function (err) {
if (err) {
Logger.error(err)
} else {
let modifyParams: string[] = [username, id]
// 改
connection.query(modifySql, modifyParams, async function (err, result) {
if (err) {
Logger.error(err)
} else {
await res.json({
code: Code.successCode,
info: Info[7]
})
}
})
}
})
})
}
/**
* @typedef DeleteList
* @property {integer} id.required - id
*/
/**
* @route DELETE /deleteList/{id}
* @summary
* @param {DeleteList.model} id.path.required - id
* @group
* @returns {object} 200
* @returns {Array.<DeleteList>} DeleteList
* @security JWT
*/
const deleteList = async (req: Request, res: Response) => {
const { id } = req.params
let payload = null
try {
const authorizationHeader = req.get("Authorization")
const accessToken = authorizationHeader.substr("Bearer ".length)
payload = jwt.verify(accessToken, secret.jwtSecret)
} catch (error) {
return res.status(401).end()
}
let sql: string = 'DELETE FROM users where id=' + "'" + id + "'"
connection.query(sql, async function (err, data) {
if (err) {
console.log(err)
} else {
await res.json({
code: Code.successCode,
info: Info[8]
})
}
})
}
/**
* @typedef SearchPage
* @property {integer} page.required - - eg: 1
* @property {integer} size.required - - eg: 5
*/
/**
* @route POST /searchPage
* @param {SearchPage.model} point.body.required - the new point
* @produces application/json application/xml
* @consumes application/json application/xml
* @summary
* @group
* @returns {Response.model} 200
* @returns {Array.<SearchPage>} SearchPage
* @headers {integer} 200.X-Rate-Limit
* @headers {string} 200.X-Expires-After
* @security JWT
*/
const searchPage = async (req: Request, res: Response) => {
const { page, size } = req.body
let payload = null
try {
const authorizationHeader = req.get("Authorization")
const accessToken = authorizationHeader.substr("Bearer ".length)
payload = jwt.verify(accessToken, secret.jwtSecret)
} catch (error) {
return res.status(401).end()
}
let sql: string = 'select * from users limit ' + size + ' offset ' + size * (page - 1)
connection.query(sql, async function (err, data) {
if (err) {
Logger.error(err)
} else {
await res.json({
code: Code.successCode,
info: data
})
}
})
}
/**
* @typedef SearchVague
* @property {string} username.required - - eg: admin
*/
/**
* @route POST /searchVague
* @param {SearchVague.model} point.body.required - the new point
* @produces application/json application/xml
* @consumes application/json application/xml
* @summary
* @group
* @returns {Response.model} 200
* @returns {Array.<SearchVague>} SearchVague
* @headers {integer} 200.X-Rate-Limit
* @headers {string} 200.X-Expires-After
* @security JWT
*/
const searchVague = async (req: Request, res: Response) => {
const { username } = req.body
let payload = null
try {
const authorizationHeader = req.get("Authorization")
const accessToken = authorizationHeader.substr("Bearer ".length)
payload = jwt.verify(accessToken, secret.jwtSecret)
} catch (error) {
return res.status(401).end()
}
if (username === "" || username === null) return res.json({
code: Code.failCode,
info: Info[9]
})
let sql: string = 'select * from users'
sql += " WHERE username LIKE " + mysql.escape("%" + username + "%")
connection.query(sql, function (err, data) {
connection.query(sql, async function (err) {
if (err) {
Logger.error(err)
} else {
await res.json({
code: Code.successCode,
info: data
})
}
})
})
}
/**
* @route GET /captcha
* @summary
* @group captcha -
* @returns {object} 200
* @security JWT
*/
const captcha = async (req: Request, res: Response) => {
const create = createMathExpr({
mathMin: 1,
mathMax: 4,
mathOperator: "+"
})
generateVerify = Number(create.text)
res.type('svg') // 响应的类型
res.json({ code: Code.successCode, info: create.text, svg: create.data })
}
export {
login,
register,
updateList,
deleteList,
searchPage,
searchVague,
captcha,
}

61
src/server.ts Normal file
View File

@ -0,0 +1,61 @@
import app from "./app"
import * as open from "open"
import config from "./config"
import { user } from "./models/mysql"
import Logger from "./loaders/logger"
import { queryTable } from "./utils/initMysql"
const expressSwagger = require("express-swagger-generator")(app)
expressSwagger(config.options)
queryTable(user)
import {
login,
register,
updateList,
deleteList,
searchPage,
searchVague,
captcha,
} from "./router/api/mysql"
app.post('/login', (req, res) => {
login(req, res)
})
app.post('/register', (req, res) => {
register(req, res)
})
app.put('/updateList/:id', (req, res) => {
updateList(req, res)
})
app.delete('/deleteList/:id', (req, res) => {
deleteList(req, res)
})
app.post('/searchPage', (req, res) => {
searchPage(req, res)
})
app.post('/searchVague', (req, res) => {
searchVague(req, res)
})
app.get('/captcha', (req, res) => {
captcha(req, res)
})
app.listen(config.port, () => {
Logger.info(`
################################################
🛡 Swagger文档地址: http://localhost:${config.port} 🛡️
################################################
`)
}).on('error', err => {
Logger.error(err)
process.exit(1)
})
open(`http://localhost:${config.port}`) // 自动打开默认浏览器

23
src/utils/date.ts Normal file
View File

@ -0,0 +1,23 @@
interface dateModel {
getMonth: () => any
getDate: () => string | number
getFullYear: () => string | number
getHours: () => string | number
getMinutes: () => string | number
getSeconds: () => string | number
}
export default async function getFormatDate(): Promise<Date | string> {
let date: dateModel = new Date()
let month: string | number = date.getMonth() + 1
let strDate = date.getDate()
if (month >= 1 && month <= 9) {
month = "0" + month
}
if (strDate >= 0 && strDate <= 9) {
strDate = "0" + strDate
}
let currentDate = date.getFullYear() + "-" + month + "-" + strDate +
" " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds()
return currentDate
}

19
src/utils/infoEnum.ts Normal file
View File

@ -0,0 +1,19 @@
// 状态码
export const enum Code {
failCode = -1,
successCode = 0
}
// 返回信息
export enum Info {
"请输入正确的验证码",
"账号尚未被注册",
"登录成功",
"密码错误",
"密码长度不能小于6位",
"账号已被注册",
"账号注册成功",
"修改成功",
"删除成功",
"搜索信息不能为空",
}

13
src/utils/initMysql.ts Normal file
View File

@ -0,0 +1,13 @@
import * as mysql from "mysql2"
import mysqlConfig from "../config"
import Logger from "../loaders/logger"
//user数据库
export const connection = mysql.createConnection(Object.assign({ database: 'user' }, mysqlConfig.mysql))
export function queryTable(s: string): void {
connection.query(s, (err) => {
err ? Logger.error(err) : Logger.info(`${s}表创建成功`)
})
}

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"pretty": true,
"sourceMap": true,
"target": "es6",
"outDir": "./dist",
"baseUrl": "./lib"
},
"include": [
"src/**/*.ts", "src/router/api/user.js"
],
"exclude": [
"node_modules",
"dist"
]
}

29
tslint.json Normal file
View File

@ -0,0 +1,29 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"interface-name": [
true,
"never-prefix"
],
"no-console": [
false
],
"semicolon": [
false
],
"quotemark": [
false
],
"object-literal-sor t-keys": [
false
],
"max-classes-per-file": [
false
]
},
"rulesDirectory": []
}