test
This commit is contained in:
commit
54f5caeeaa
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
24
js-demo/client/.gitignore
vendored
Normal file
24
js-demo/client/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
3
js-demo/client/.vscode/extensions.json
vendored
Normal file
3
js-demo/client/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["johnsoncodehk.volar"]
|
||||
}
|
5
js-demo/client/README.md
Normal file
5
js-demo/client/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
### DEMO
|
||||
|
||||
此处为验证API的调试环境
|
||||
|
||||
By Vite
|
13
js-demo/client/index.html
Normal file
13
js-demo/client/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
2694
js-demo/client/package-lock.json
generated
Normal file
2694
js-demo/client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
js-demo/client/package.json
Normal file
22
js-demo/client/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "poros-demo-client",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite --port 8888 --host 0.0.0.0 ",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.2.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@originjs/vite-plugin-commonjs": "1.0.3",
|
||||
"@types/node": "17.0.25",
|
||||
"@vitejs/plugin-vue": "2.3.0",
|
||||
"axios": "0.26.1",
|
||||
"typescript": "4.5.4",
|
||||
"vite": "2.9.0",
|
||||
"vue-tsc": "0.29.8"
|
||||
}
|
||||
}
|
BIN
js-demo/client/public/favicon.ico
Normal file
BIN
js-demo/client/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
196
js-demo/client/src/App.vue
Normal file
196
js-demo/client/src/App.vue
Normal file
@ -0,0 +1,196 @@
|
||||
<script setup lang="ts">
|
||||
// This starter template is using Vue 3 <script setup> SFCs
|
||||
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
|
||||
import { ref } from "vue"
|
||||
import axios from "axios"
|
||||
import { createSocket, destroySocket } from "./socket/index"
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: "http://localhost:3000",
|
||||
})
|
||||
|
||||
// 替换你的秘钥
|
||||
const appKey = ref("")
|
||||
const appSecret = ref("")
|
||||
// 替换你的主播身份码
|
||||
const codeId = ref("")
|
||||
// 替换你的app应用 [这里测试为互动游戏]
|
||||
const appId = ref("")
|
||||
// [向 node server请求接口后自动返回]
|
||||
const gameId = ref("")
|
||||
// v2改为server response 服务器返回websocket信息,而非手动获取
|
||||
const authBody = ref("")
|
||||
const wssLinks = ref([])
|
||||
// heartBeat Timer
|
||||
const heartBeatTimer = ref<NodeJS.Timer>()
|
||||
// be ready
|
||||
clearInterval(heartBeatTimer.value!)
|
||||
/**
|
||||
* 测试请求鉴权接口
|
||||
*/
|
||||
const getAuth = () => {
|
||||
api.post("/getAuth", {
|
||||
appKey: appKey.value,
|
||||
appSecret: appSecret.value,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
console.log("-----鉴权成功-----")
|
||||
console.log("返回:", data)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----鉴权失败-----")
|
||||
})
|
||||
}
|
||||
|
||||
const heartBeatThis = (game_id) => {
|
||||
// 心跳 是否成功
|
||||
api.post("/gameHeartBeat", {
|
||||
game_id,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
console.log("-----心跳成功-----")
|
||||
console.log("返回:", data)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----心跳失败-----")
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @comment 注意所有的接口基于鉴权成功后才能正确返回
|
||||
* 测试请求游戏开启接口
|
||||
*/
|
||||
const gameStart = () => {
|
||||
api.post("/gameStart", {
|
||||
code: codeId.value,
|
||||
app_id: Number(appId.value),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.code === 0) {
|
||||
const res = data.data
|
||||
const { game_info, websocket_info } = res
|
||||
const { auth_body, wss_link } = websocket_info
|
||||
authBody.value = auth_body
|
||||
wssLinks.value = wss_link
|
||||
console.log("-----游戏开始成功-----")
|
||||
console.log("返回GameId:", game_info)
|
||||
gameId.value = game_info.game_id
|
||||
// v2改为20s请求心跳一次,不然60s会自动关闭
|
||||
heartBeatTimer.value = setInterval(() => {
|
||||
heartBeatThis(game_info.game_id)
|
||||
}, 20000)
|
||||
} else {
|
||||
console.log("-----游戏开始失败-----")
|
||||
console.log("原因:", data)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----游戏开始失败-----")
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @comment 基于gameStart成功后才会关闭正常,否则获取不到game_id
|
||||
* 测试请求游戏关闭接口
|
||||
*/
|
||||
const gameEnd = () => {
|
||||
api.post("/gameEnd", {
|
||||
game_id: gameId.value,
|
||||
app_id: Number(appId.value),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.code === 0) {
|
||||
console.log("-----游戏关闭成功-----")
|
||||
console.log("返回:", data)
|
||||
// 清空长链
|
||||
authBody.value = ""
|
||||
wssLinks.value = []
|
||||
clearInterval(heartBeatTimer.value)
|
||||
handleDestroySocket()
|
||||
console.log("-----心跳关闭成功-----")
|
||||
} else {
|
||||
console.log("-----游戏关闭失败-----")
|
||||
console.log("原因:", data)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----游戏关闭失败-----")
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试创建长长连接接口
|
||||
*/
|
||||
const handleCreateSocket = () => {
|
||||
if (authBody.value && wssLinks.value) {
|
||||
createSocket(authBody.value, wssLinks.value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试销毁长长连接接口
|
||||
*/
|
||||
const handleDestroySocket = () => {
|
||||
destroySocket()
|
||||
console.log("-----长连接销毁成功-----")
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>-- !-- 所有输出信息都在控制台 -- --</h3>
|
||||
<div>鉴权部分</div>
|
||||
<div class="form">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="填写access_key_id"
|
||||
v-model="appKey"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="填写access_key_secret"
|
||||
v-model="appSecret"
|
||||
/>
|
||||
<button @click="getAuth">鉴权</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div>程序开启部分 [需要先鉴权]</div>
|
||||
<div class="form">
|
||||
<input type="text" placeholder="填写主播身份码" v-model="codeId" />
|
||||
<input type="text" placeholder="填写 app_id" v-model="appId" />
|
||||
<button @click="gameStart">游戏开始</button>
|
||||
<button @click="gameEnd">游戏结束</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div>长连接部分 [需要先游戏开启]</div>
|
||||
<div class="form">
|
||||
<button @click="handleCreateSocket">建立长连接</button>
|
||||
<button @click="handleDestroySocket">销毁长连接</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.form input,
|
||||
.form button {
|
||||
width: 400px;
|
||||
height: 50px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
8
js-demo/client/src/assets/danmaku-websocket.min.js
vendored
Normal file
8
js-demo/client/src/assets/danmaku-websocket.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
9
js-demo/client/src/env.d.ts
vendored
Normal file
9
js-demo/client/src/env.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
5
js-demo/client/src/main.ts
Normal file
5
js-demo/client/src/main.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { createApp } from "vue"
|
||||
import App from "./App.vue"
|
||||
|
||||
createApp(App).mount("#app")
|
||||
|
80
js-demo/client/src/socket/index.ts
Normal file
80
js-demo/client/src/socket/index.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import DanmakuWebSocket from "../assets/danmaku-websocket.min.js"
|
||||
|
||||
let ws: DanmakuWebSocket
|
||||
|
||||
/**
|
||||
* 创建socket长连接
|
||||
* @param authBody
|
||||
* @param wssLinks
|
||||
*/
|
||||
function createSocket(authBody: string, wssLinks: string[]) {
|
||||
const opt = {
|
||||
...getWebSocketConfig(authBody, wssLinks),
|
||||
// 收到消息,
|
||||
onReceivedMessage: (res) => {
|
||||
console.log(res)
|
||||
},
|
||||
// 收到心跳处理回调
|
||||
onHeartBeatReply: (data) => console.log("收到心跳处理回调:", data),
|
||||
onError: (data) => console.log("error", data),
|
||||
onListConnectError: () => {
|
||||
console.log("list connect error")
|
||||
destroySocket()
|
||||
},
|
||||
}
|
||||
|
||||
if (!ws) {
|
||||
ws = new DanmakuWebSocket(opt)
|
||||
}
|
||||
|
||||
return ws
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取websocket配置信息
|
||||
* @param authBody
|
||||
* @param wssLinks
|
||||
*/
|
||||
function getWebSocketConfig(authBody: string, wssLinks: string[]) {
|
||||
const url = wssLinks[0]
|
||||
const urlList = wssLinks
|
||||
const auth_body = JSON.parse(authBody)
|
||||
return {
|
||||
url,
|
||||
urlList,
|
||||
customAuthParam: [
|
||||
{
|
||||
key: "key",
|
||||
value: auth_body.key,
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
key: "group",
|
||||
value: auth_body.group,
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
rid: auth_body.roomid,
|
||||
protover: auth_body.protoover,
|
||||
uid: auth_body.uid,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁websocket
|
||||
*/
|
||||
function destroySocket() {
|
||||
console.log("destroy1")
|
||||
ws && ws.destroy()
|
||||
ws = undefined
|
||||
console.log("destroy2")
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取websocket实例
|
||||
*/
|
||||
function getWsClient() {
|
||||
return ws
|
||||
}
|
||||
|
||||
export { createSocket, destroySocket, getWebSocketConfig, getWsClient }
|
19
js-demo/client/src/types/index.d.ts
vendored
Normal file
19
js-demo/client/src/types/index.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
declare interface ISocketData {
|
||||
// ip地址
|
||||
ip: number[]
|
||||
|
||||
// host地址 可能是ip 也可能是域名。
|
||||
host: string[]
|
||||
|
||||
// 长连使用的请求json体 第三方无需关注内容,建立长连时使用即可。
|
||||
auth_body: string
|
||||
|
||||
// tcp 端口号
|
||||
tcp_port: number[]
|
||||
|
||||
// ws 端口号
|
||||
ws_port: number[]
|
||||
|
||||
// wss 端口号
|
||||
wss_port: number[]
|
||||
}
|
20
js-demo/client/tsconfig.json
Normal file
20
js-demo/client/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
"types": ["node"],
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noImplicitAny": false,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
8
js-demo/client/tsconfig.node.json
Normal file
8
js-demo/client/tsconfig.node.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
17
js-demo/client/vite.config.ts
Normal file
17
js-demo/client/vite.config.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { defineConfig } from "vite"
|
||||
import vue from "@vitejs/plugin-vue"
|
||||
import { viteCommonjs } from "@originjs/vite-plugin-commonjs"
|
||||
import * as path from "path"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
// https: true
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src")
|
||||
}
|
||||
},
|
||||
plugins: [vue(), viteCommonjs()]
|
||||
})
|
5
js-demo/server/README.md
Normal file
5
js-demo/server/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
### 说明
|
||||
|
||||
基于 npm 16.13.0 构建
|
||||
|
||||
koa server服务
|
22
js-demo/server/interceptor.ts
Normal file
22
js-demo/server/interceptor.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import axios from "axios"
|
||||
import { getEncodeHeader } from "./tool/index"
|
||||
|
||||
// axios 拦截器
|
||||
const api = axios.create({
|
||||
baseURL: "https://live-open.biliapi.com"
|
||||
// baseURL: "http://test-live-open.biliapi.net" //test
|
||||
})
|
||||
|
||||
// 鉴权加密处理headers,下次请求自动带上
|
||||
api.interceptors.request.use(config => {
|
||||
const headers = getEncodeHeader(
|
||||
config.data,
|
||||
global.appKey,
|
||||
global.appSecret
|
||||
)
|
||||
console.log('headers: ', headers)
|
||||
config.headers = headers
|
||||
return config
|
||||
})
|
||||
|
||||
export default api
|
1777
js-demo/server/package-lock.json
generated
Normal file
1777
js-demo/server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
js-demo/server/package.json
Normal file
28
js-demo/server/package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "poros-demo-server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "server.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"server": "node --loader ts-node/esm --experimental-specifier-resolution=node server.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "sometimes & re",
|
||||
"type": "module",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"axios": "0.26.1",
|
||||
"http": "0.0.1-security",
|
||||
"https": "1.0.0",
|
||||
"koa": "2.13.4",
|
||||
"koa-bodyparser": "4.3.0",
|
||||
"koa-logger": "3.2.1",
|
||||
"koa-router": "10.1.1",
|
||||
"koa2-cors": "2.0.6",
|
||||
"ts-node": "10.8.0",
|
||||
"tslib": "2.3.1",
|
||||
"typescript": "4.6.3",
|
||||
"@types/node": "17.0.25"
|
||||
}
|
||||
}
|
17
js-demo/server/routes/gameBatchHeartBeat.ts
Normal file
17
js-demo/server/routes/gameBatchHeartBeat.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import api from "../interceptor"
|
||||
|
||||
/**
|
||||
* 批量心跳接口
|
||||
* @param ctx
|
||||
*/
|
||||
export default async function GameBatchHeartBeat(ctx) {
|
||||
const params = ctx.request.body
|
||||
await api
|
||||
.post("/v2/app/batchHeartbeat", params)
|
||||
.then(({ data }) => {
|
||||
ctx.body = data
|
||||
})
|
||||
.catch(err => {
|
||||
ctx.body = err
|
||||
})
|
||||
}
|
17
js-demo/server/routes/gameEnd.ts
Normal file
17
js-demo/server/routes/gameEnd.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import api from "../interceptor"
|
||||
|
||||
/**
|
||||
* 互动玩法游戏结束接口
|
||||
* @param ctx
|
||||
*/
|
||||
export default async function GameEnd(ctx) {
|
||||
const params = ctx.request.body
|
||||
await api
|
||||
.post("/v2/app/end", params)
|
||||
.then(({ data }) => {
|
||||
ctx.body = data
|
||||
})
|
||||
.catch(err => {
|
||||
ctx.body = err
|
||||
})
|
||||
}
|
17
js-demo/server/routes/gameHeartBeat.ts
Normal file
17
js-demo/server/routes/gameHeartBeat.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import api from "../interceptor"
|
||||
|
||||
/**
|
||||
* 心跳接口
|
||||
* @param ctx
|
||||
*/
|
||||
export default async function GameHeartBeat(ctx) {
|
||||
const params = ctx.request.body
|
||||
await api
|
||||
.post("/v2/app/heartbeat", params)
|
||||
.then(({ data }) => {
|
||||
ctx.body = data
|
||||
})
|
||||
.catch(err => {
|
||||
ctx.body = err
|
||||
})
|
||||
}
|
17
js-demo/server/routes/gameStart.ts
Normal file
17
js-demo/server/routes/gameStart.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import api from "../interceptor"
|
||||
|
||||
/**
|
||||
* 互动玩法游戏结束接口
|
||||
* @param ctx
|
||||
*/
|
||||
export default async function GameStart(ctx) {
|
||||
const params = ctx.request.body
|
||||
await api
|
||||
.post("/v2/app/start", params)
|
||||
.then(({ data }) => {
|
||||
ctx.body = data
|
||||
})
|
||||
.catch(err => {
|
||||
ctx.body = err
|
||||
})
|
||||
}
|
30
js-demo/server/routes/getAuth.ts
Normal file
30
js-demo/server/routes/getAuth.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import api from "../interceptor"
|
||||
|
||||
/**
|
||||
* 鉴权签名接口
|
||||
* @param ctx
|
||||
*/
|
||||
export default async function GetAuth(ctx) {
|
||||
const params = ctx.request.body
|
||||
// 为了方便存在全局
|
||||
global.appKey = params.appKey
|
||||
global.appSecret = params.appSecret
|
||||
// [只为验证鉴权,可删除]
|
||||
await api
|
||||
.post("/v2/app/start", {})
|
||||
.then(({ data }) => {
|
||||
// 非文档描述性code,sign success
|
||||
if (data.code === -400) {
|
||||
ctx.body = {
|
||||
msg: "auth success",
|
||||
type: "success",
|
||||
state: 200
|
||||
}
|
||||
} else {
|
||||
ctx.body = data
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
ctx.body = err
|
||||
})
|
||||
}
|
17
js-demo/server/routes/index.ts
Normal file
17
js-demo/server/routes/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import Router from "koa-router"
|
||||
import GetAuth from "./getAuth"
|
||||
import GameStart from "./gameStart"
|
||||
import GameEnd from "./gameEnd"
|
||||
import GameHeartBeat from "./gameHeartBeat"
|
||||
import GameBatchHeartBeat from "./gameBatchHeartBeat"
|
||||
|
||||
const router = new Router()
|
||||
|
||||
// 开启路由
|
||||
router.post("/getAuth", GetAuth)
|
||||
router.post("/gameStart", GameStart)
|
||||
router.post("/gameEnd", GameEnd)
|
||||
router.post("/gameHeartBeat", GameHeartBeat)
|
||||
router.post("/gameBatchHeartBeat", GameBatchHeartBeat)
|
||||
|
||||
export default router
|
37
js-demo/server/server.ts
Normal file
37
js-demo/server/server.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import Koa from "koa"
|
||||
import logger from "koa-logger"
|
||||
import cors from "koa2-cors"
|
||||
import bodyParser from "koa-bodyparser"
|
||||
|
||||
import router from "./routes/index"
|
||||
|
||||
// axios 拦截
|
||||
import "./interceptor"
|
||||
|
||||
// init
|
||||
const app = new Koa()
|
||||
|
||||
// 开启logger
|
||||
app.use(logger())
|
||||
|
||||
// 开启bodyParser
|
||||
app.use(bodyParser())
|
||||
|
||||
// 开启跨域
|
||||
app.use(cors())
|
||||
|
||||
// 开启router
|
||||
app.use(router.routes())
|
||||
app.use(router.allowedMethods())
|
||||
|
||||
app.use(async ctx => {
|
||||
ctx.body = "bilibili创作者服务中心"
|
||||
})
|
||||
|
||||
const protocol = "http"
|
||||
const host = "127.0.0.1"
|
||||
const port = 3000
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Listening on ${protocol}://${host}:${port}`)
|
||||
})
|
50
js-demo/server/tool/index.ts
Normal file
50
js-demo/server/tool/index.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import crypto from "crypto"
|
||||
|
||||
/**
|
||||
* 鉴权加密
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function getEncodeHeader(
|
||||
params = {},
|
||||
appKey: string,
|
||||
appSecret: string
|
||||
) {
|
||||
const timestamp = parseInt(Date.now() / 1000 + "")
|
||||
const nonce = parseInt(Math.random() * 100000 + "") + timestamp
|
||||
const header = {
|
||||
"x-bili-accesskeyid": appKey,
|
||||
"x-bili-content-md5": getMd5Content(JSON.stringify(params)),
|
||||
"x-bili-signature-method": "HMAC-SHA256",
|
||||
"x-bili-signature-nonce": nonce + "",
|
||||
"x-bili-signature-version": "1.0",
|
||||
"x-bili-timestamp": timestamp
|
||||
}
|
||||
const data: string[] = []
|
||||
for (const key in header) {
|
||||
data.push(`${key}:${header[key]}`)
|
||||
}
|
||||
|
||||
const signature = crypto
|
||||
.createHmac("sha256", appSecret)
|
||||
.update(data.join("\n"))
|
||||
.digest("hex")
|
||||
return {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
...header,
|
||||
Authorization: signature
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MD5加密
|
||||
* @param {*} str
|
||||
* @returns
|
||||
*/
|
||||
export function getMd5Content(str) {
|
||||
return crypto
|
||||
.createHash("md5")
|
||||
.update(str)
|
||||
.digest("hex")
|
||||
}
|
48
js-demo/server/tsconfig.json
Normal file
48
js-demo/server/tsconfig.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016",
|
||||
/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
|
||||
/* Modules */
|
||||
"module": "ESNext",
|
||||
/* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "node",
|
||||
/* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
"typeRoots": ["./types"],
|
||||
/* Specify multiple folders that act like `./node_modules/@types`. */
|
||||
"types": ["node"],
|
||||
/* Specify type package names to be included without being referenced in a source file. */
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true,
|
||||
/* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noImplicitAny": false,
|
||||
/* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true,
|
||||
/* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
/* Ensure that casing is correct in imports. */
|
||||
/* Type Checking */
|
||||
"strict": true
|
||||
/* Enable all strict type-checking options. */
|
||||
/* Completeness */
|
||||
// "declaration": true // 生成*.d.ts
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
// "skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"ts-node": {
|
||||
"esm": true
|
||||
}
|
||||
}
|
14
package.json
Normal file
14
package.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "bilibili-open-demo",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.6.2",
|
||||
"type": "module"
|
||||
}
|
1968
pnpm-lock.yaml
generated
Normal file
1968
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- 'packages/*'
|
||||
- 'js-demo/*'
|
Loading…
x
Reference in New Issue
Block a user