[mod] TSRPC
This commit is contained in:
parent
f7559a5f27
commit
e743a53f18
9
.env
Normal file
9
.env
Normal file
@ -0,0 +1,9 @@
|
||||
URLPATH = /linewebhook
|
||||
PORT = 3003
|
||||
|
||||
# DB----------------------------------------------
|
||||
DB_HOST = 192.168.0.15
|
||||
DB_PORT = 3307
|
||||
DB_USER = jianmiau
|
||||
DB_PASSWORD = VQ*ZetC7xcc9%dTW
|
||||
DB_DATABASE = badminton
|
368
.eslintrc.json
Normal file
368
.eslintrc.json
Normal file
@ -0,0 +1,368 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"jest": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"overrides": [],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"@typescript-eslint",
|
||||
"react-hooks",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"no-alert": 0, //禁止使用alert confirm prompt
|
||||
"no-bitwise": 0, //禁止使用按位运算符
|
||||
"no-console": "off", //禁止使用console
|
||||
"no-continue": 0, //禁止使用continue
|
||||
"no-debugger": 2, //禁止使用debugger
|
||||
"no-delete-var": 2, //不能对var声明的变量使用delete操作符
|
||||
"no-div-regex": 1, //不能使用看起来像除法的正则表达式/=foo/
|
||||
"no-dupe-args": 2, //函数参数不能重复
|
||||
"no-duplicate-case": 2, //switch中的case标签不能重复
|
||||
"no-else-return": "off", //如果if语句里面有return,后面不能跟else语句
|
||||
"no-empty-label": "off", //禁止使用空label
|
||||
"no-eq-null": "off", //禁止对null使用==或!=运算符
|
||||
"no-extend-native": "off", //禁止扩展native对象
|
||||
"no-extra-parens": "off", //禁止非必要的括号
|
||||
"no-extra-semi": 2, //禁止多余的冒号
|
||||
"no-floating-decimal": 2, //禁止省略浮点数中的0 .5 3.
|
||||
"no-implicit-coercion": "off", //禁止隐式转换
|
||||
"no-inline-comments": 0, //禁止行内备注
|
||||
"no-invalid-this": 2, //禁止无效的this,只能用在构造器,类,对象字面量
|
||||
"no-iterator": 2, //禁止使用__iterator__ 属性
|
||||
"no-lonely-if": "off", //禁止else语句内只有if语句
|
||||
"no-mixed-requires": [
|
||||
0,
|
||||
false
|
||||
], //声明时不能混用声明类型
|
||||
"no-mixed-spaces-and-tabs": "off", //禁止混用tab和空格
|
||||
"no-multiple-empty-lines": [
|
||||
1,
|
||||
{
|
||||
"max": 2
|
||||
}
|
||||
], //空行最多不能超过2行
|
||||
"no-nested-ternary": 0, //禁止使用嵌套的三目运算
|
||||
"no-new": "off", //禁止在使用new构造一个实例后不赋值
|
||||
"no-new-require": 2, //禁止使用new require
|
||||
"no-param-reassign": "off", //禁止给参数重新赋值
|
||||
"no-path-concat": 0, //node中不能使用__dirname或__filename做路径拼接
|
||||
"no-plusplus": 0, //禁止使用++,--
|
||||
"no-process-env": 0, //禁止使用process.env
|
||||
"no-process-exit": 0, //禁止使用process.exit()
|
||||
"no-redeclare": "off", //禁止重复声明变量
|
||||
"no-restricted-modules": 0, //如果禁用了指定模块,使用就会报错
|
||||
"no-return-assign": "off", //return 语句中不能有赋值表达式
|
||||
"no-self-compare": 2, //不能比较自身
|
||||
"no-sequences": 0, //禁止使用逗号运算符
|
||||
"no-shadow": "off", //外部作用域中的变量不能与它所包含的作用域中的变量或参数同名
|
||||
"no-sync": 0, //nodejs 禁止同步方法
|
||||
"no-ternary": 0, //禁止使用三目运算符
|
||||
"no-this-before-super": 0, //在调用super()之前不能使用this或super
|
||||
"no-throw-literal": 2, //禁止抛出字面量错误 throw "error";
|
||||
"no-undef": "off", //不能有未定义的变量
|
||||
"no-undef-init": "off", //变量初始化时不能直接给它赋值为undefined
|
||||
"no-undefined": "off", //不能使用undefined
|
||||
"no-unexpected-multiline": 2, //避免多行表达式
|
||||
"no-underscore-dangle": "off", //标识符不能以_开头或结尾
|
||||
"no-unneeded-ternary": 2, //禁止不必要的嵌套 var isYes = answer === 1 ? true : false;
|
||||
"no-unused-expressions": "off", //禁止无用的表达式
|
||||
"no-unused-vars": "off", //不能有声明后未被使用的变量或参数
|
||||
"no-use-before-define": "off", //未定义前不能使用
|
||||
"no-useless-call": "off", //禁止不必要的call和apply
|
||||
"no-void": "off", //禁用void操作符
|
||||
"no-var": 0, //禁用var,用let和const代替
|
||||
"no-warning-comments": "off", //不能有警告备注
|
||||
"no-array-constructor": "error", // 禁止使用数组构造器
|
||||
"no-caller": "error", // 禁止使用arguments.caller或arguments.callee
|
||||
"no-catch-shadow": "error", // 禁止catch子句参数与外部作用域变量同名
|
||||
"no-class-assign": "error", // 禁止给类赋值
|
||||
"no-cond-assign": [
|
||||
"error",
|
||||
"except-parens"
|
||||
], // 禁止在条件表达式中使用赋值语句
|
||||
"no-constant-condition": "error", // 禁止在条件中使用常量表达式 if(true) if(1)
|
||||
"no-control-regex": "error", // 禁止在正则表达式中使用控制字符
|
||||
"no-dupe-keys": "error", // 在创建对象字面量时不允许键重复 {a: 1, a: 1}
|
||||
"no-empty": "error", // 块语句中的内容不能为空
|
||||
"no-empty-character-class": "error", // 正则表达式中的[]内容不能为空
|
||||
"no-eval": "error", // 禁止使用eval
|
||||
"no-ex-assign": "error", // 禁止给catch语句中的异常参数赋值
|
||||
"no-extra-bind": "error", // 禁止不必要的函数绑定
|
||||
"no-extra-boolean-cast": "off", // 禁止不必要的bool转换
|
||||
"no-fallthrough": "error", // 禁止switch穿透
|
||||
"no-func-assign": "error", // 禁止重复的函数声明
|
||||
"no-implied-eval": "error", // 禁止使用隐式eval
|
||||
"no-inner-declarations": "off", // 禁止在块语句中使用声明函数
|
||||
"no-invalid-regexp": "error", // 禁止无效的正则表达式
|
||||
"no-irregular-whitespace": "error", // 不能有不规则的空格
|
||||
"no-label-var": "error", // label名不能与var声明的变量名相同
|
||||
"no-labels": "error", // 禁止标签声明
|
||||
"no-lone-blocks": "error", // 禁止不必要的嵌套块
|
||||
"no-loop-func": "error", // 禁止在循环中使用函数(如果没有引用外部变量不形成闭包就可以)
|
||||
"no-multi-spaces": "error", // 不能用多余的空格
|
||||
"no-multi-str": "error", // 字符串不能用\换行
|
||||
"no-native-reassign": "error", // 不能重写native对象
|
||||
"no-negated-in-lhs": "error", // in 操作符的左边不能有!
|
||||
"no-new-func": "error", // 禁止使用new Function
|
||||
"no-new-object": "error", // 禁止使用new Object()
|
||||
"no-new-wrappers": "error", // 禁止使用new创建包装实例,new String new Boolean new Number
|
||||
"no-obj-calls": "error", // 不能调用内置的全局对象,比如Math() JSON()
|
||||
"no-octal": "error", // 禁止使用八进制数字(因为八进制数字以0开头)
|
||||
"no-octal-escape": "error", // 禁止使用八进制转义序列
|
||||
"no-proto": "error", // 禁止使用__proto__属性(按照标准,__proto__为私有属性,不应公开)
|
||||
"no-regex-spaces": "error", // 禁止在正则表达式字面量中使用多个空格 /foo bar/
|
||||
"no-script-url": "off", // 禁止使用javascript:void(0)
|
||||
"no-shadow-restricted-names": "error", // 严格模式中规定的限制标识符不能作为声明时的变量名使用
|
||||
"no-spaced-func": "error", // 函数调用时 函数名与()之间不能有空格
|
||||
"no-sparse-arrays": "error", // 禁止稀疏数组, [1,,2]
|
||||
"no-trailing-spaces": [
|
||||
"error",
|
||||
{
|
||||
"skipBlankLines": true
|
||||
}
|
||||
], // 一行结束后面不要有空格( 空白行忽略 )
|
||||
"no-unreachable": "error", // 不能有无法执行的代码
|
||||
"no-const-assign": "error", // 禁止修改const声明的变量
|
||||
"no-with": "error", // 禁用with
|
||||
"comma-dangle": "off", // 数组或对象最后不允许出现多余的逗号
|
||||
"comma-spacing": "error", // 逗号前面不允许有空格,后面还有东西的时候必须有一个空格
|
||||
"curly": [
|
||||
"error",
|
||||
"multi-line"
|
||||
], // 块级代码需要换行的时候必须使用 {}将代码裹起来
|
||||
"eqeqeq": "off", // 必须使用全等
|
||||
"indent": [
|
||||
"off",
|
||||
"tab",
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
], // 缩进用tab
|
||||
"key-spacing": [
|
||||
"error",
|
||||
{
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}
|
||||
], // 对象字面量中冒号的后面必须有空格,前面不允许有空格
|
||||
"keyword-spacing": "off", // 关键字前后必须存在空格
|
||||
"new-parens": "error", // new时必须加小括号 const person = new Person();
|
||||
"quotes": [
|
||||
"error",
|
||||
"double",
|
||||
{
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
], // 引号类型 ''
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
], // 语句必须分号结尾
|
||||
"semi-spacing": [
|
||||
0,
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
], // 分号前面不允许有空格,后面有其他东西的时候必须空一空格
|
||||
"space-before-blocks": [
|
||||
"error",
|
||||
"always"
|
||||
], // 不以新行开始的块 { 前面要有空格
|
||||
// "space-before-function-paren": ["error", "never"], // 函数定义时括号前面不允许有空格
|
||||
"space-infix-ops": "error", // 中缀操作符周围必须有空格 a + b
|
||||
"space-unary-ops": [
|
||||
"error",
|
||||
{
|
||||
"words": true,
|
||||
"nonwords": false
|
||||
}
|
||||
], // 一元运算符的前/后如果是单词则空一空格,如果是运算符则不需要空空格 new Foo √ 1++ √
|
||||
// "spaced-comment": ["error", "always", { "markers": ["*!"] }], // 注释风格, 双斜杠后面空一格空格再写注释
|
||||
"strict": [
|
||||
"error",
|
||||
"global"
|
||||
], // 使用全局严格模式
|
||||
"use-isnan": "error", // 禁止比较时使用NaN,只能用isNaN()
|
||||
"arrow-parens": 0, //箭头函数用小括号括起来
|
||||
"arrow-spacing": 0, //=>的前/后括号
|
||||
"accessor-pairs": 0, //在对象中使用getter/setter
|
||||
"block-scoped-var": 0, //块语句中使用var
|
||||
"brace-style": "off", //大括号风格
|
||||
"callback-return": "off", //避免多次调用回调什么的
|
||||
"comma-style": [
|
||||
"error",
|
||||
"last"
|
||||
], //逗号风格,换行时在行首还是行尾
|
||||
"complexity": [
|
||||
0,
|
||||
11
|
||||
], //循环复杂度
|
||||
"computed-property-spacing": [
|
||||
0,
|
||||
"never"
|
||||
], //是否允许计算后的键名什么的
|
||||
"consistent-return": 0, //return 后面是否允许省略
|
||||
"consistent-this": "off", //this别名
|
||||
"constructor-super": 0, //非派生类不能调用super,派生类必须调用super
|
||||
"default-case": "off", //switch语句最后必须有default
|
||||
"dot-location": 0, //对象访问符的位置,换行的时候在行首还是行尾
|
||||
"dot-notation": [
|
||||
0,
|
||||
{
|
||||
"allowKeywords": true
|
||||
}
|
||||
], //避免不必要的方括号
|
||||
"eol-last": 0, //文件以单一的换行符结束
|
||||
"func-names": 0, //函数表达式必须有名字
|
||||
"func-style": [
|
||||
0,
|
||||
"declaration"
|
||||
], //函数风格,规定只能使用函数声明/函数表达式
|
||||
"generator-star-spacing": 0, //生成器函数*的前后空格
|
||||
"guard-for-in": 0, //for in循环要用if语句过滤
|
||||
"handle-callback-err": 0, //nodejs 处理错误
|
||||
"id-length": 0, //变量名长度
|
||||
"init-declarations": 0, //声明时必须赋初值
|
||||
"lines-around-comment": 0, //行前/行后备注
|
||||
"max-depth": [
|
||||
0,
|
||||
4
|
||||
], //嵌套块深度
|
||||
"max-len": [
|
||||
0,
|
||||
80,
|
||||
4
|
||||
], //字符串最大长度
|
||||
"max-nested-callbacks": [
|
||||
0,
|
||||
2
|
||||
], //回调嵌套深度
|
||||
"max-params": [
|
||||
0,
|
||||
3
|
||||
], //函数最多只能有3个参数
|
||||
"max-statements": [
|
||||
0,
|
||||
10
|
||||
], //函数内最多有几个声明
|
||||
"new-cap": "off", //函数名首行大写必须使用new方式调用,首行小写必须用不带new方式调用
|
||||
"newline-after-var": "off", //变量声明后是否需要空一行
|
||||
"object-shorthand": 0, //强制对象字面量缩写语法
|
||||
"one-var": "off", //连续声明
|
||||
"operator-assignment": [
|
||||
0,
|
||||
"always"
|
||||
], //赋值运算符 += -=什么的
|
||||
"operator-linebreak": "off", //换行时运算符在行尾还是行首
|
||||
"padded-blocks": 0, //块语句内行首行尾是否要空行
|
||||
"prefer-spread": 0, //首选展开运算
|
||||
"prefer-reflect": 0, //首选Reflect的方法
|
||||
"quote-props": "off", //对象字面量中的属性名是否强制双引号
|
||||
"radix": "off", //parseInt必须指定第二个参数
|
||||
"id-match": 0, //命名检测
|
||||
"sort-vars": 0, //变量声明时排序
|
||||
"space-after-keywords": [
|
||||
0,
|
||||
"always"
|
||||
], //关键字后面是否要空一格
|
||||
"space-before-function-paren": [
|
||||
0,
|
||||
"always"
|
||||
], //函数定义时括号前面要不要有空格
|
||||
"space-in-parens": [
|
||||
0,
|
||||
"never"
|
||||
], //小括号里面要不要有空格
|
||||
"space-return-throw-case": "off", //return throw case后面要不要加空格
|
||||
"spaced-comment": 0, //注释风格不要有空格什么的
|
||||
"valid-jsdoc": 0, //jsdoc规则
|
||||
"valid-typeof": "error", //必须使用合法的typeof的值
|
||||
"vars-on-top": "error", //var必须放在作用域顶部
|
||||
"wrap-iife": [
|
||||
"error",
|
||||
"inside"
|
||||
], //立即执行函数表达式的小括号风格
|
||||
"wrap-regex": 0, //正则表达式字面量用小括号包起来
|
||||
"yoda": [
|
||||
"error",
|
||||
"never"
|
||||
], //禁止尤达条件
|
||||
"linebreak-style": [
|
||||
0,
|
||||
"windows"
|
||||
], //换行风格
|
||||
"array-bracket-spacing": [
|
||||
2,
|
||||
"never"
|
||||
], //是否允许非空数组里面有多余的空格
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"camelcase": "off",
|
||||
"block-spacing": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"require-yield": "off",
|
||||
"prefer-const": "off",
|
||||
"object-curly-spacing": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"react/jsx-curly-spacing": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"@typescript-eslint/no-namespace": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/prefer-namespace-keyword": "off",
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"off",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/typedef": [
|
||||
"warn",
|
||||
{
|
||||
"arrayDestructuring": false,
|
||||
"arrowParameter": false,
|
||||
"objectDestructuring": false,
|
||||
"memberVariableDeclaration": true,
|
||||
"parameter": true,
|
||||
"propertyDeclaration": true,
|
||||
"variableDeclaration": false,
|
||||
"variableDeclarationIgnoreFunction": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"typescript": {}
|
||||
},
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,7 +1,3 @@
|
||||
node_modules
|
||||
.env
|
||||
package-lock.json
|
||||
*.pem
|
||||
.foreverignore
|
||||
.vscode
|
||||
/yarn.lock
|
||||
dist
|
||||
.DS_STORE
|
11
.mocharc.js
Normal file
11
.mocharc.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
require: [
|
||||
'ts-node/register',
|
||||
],
|
||||
timeout: 999999,
|
||||
exit: true,
|
||||
spec: [
|
||||
'./test/**/*.test.ts'
|
||||
],
|
||||
'preserve-symlinks': true
|
||||
}
|
22
.vscode/launch.json
vendored
Normal file
22
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
// 使用 IntelliSense 以得知可用的屬性。
|
||||
// 暫留以檢視現有屬性的描述。
|
||||
// 如需詳細資訊,請瀏覽: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach to Remote",
|
||||
// "address": "jianmiau.tk:9229/87f42d5b-97bf-4d25-a4d7-37306985459a",
|
||||
"address": "192.168.5.36:9229/87f42d5b-97bf-4d25-a4d7-37306985459a",
|
||||
"port": 9229,
|
||||
"localRoot": "${workspaceFolder}",
|
||||
"remoteRoot": "D:/Users/JianMiau/Downloads/guesswhoiams/backend",
|
||||
// "remoteRoot": "/volume1/homes/JianMiau/www/line-bot-ts",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
}
|
47
Dockerfile
47
Dockerfile
@ -1,25 +1,30 @@
|
||||
# sudo docker build -t linebotts .
|
||||
# sudo docker exec -it 2e8e3995aa52 /bin/bash
|
||||
FROM node
|
||||
|
||||
# 選擇node
|
||||
FROM node:19.4.0
|
||||
# 使用淘宝 NPM 镜像(国内机器构建推荐启用)
|
||||
# RUN npm config set registry https://registry.npm.taobao.org/
|
||||
|
||||
# 指定NODE_ENV為production
|
||||
ENV NODE_ENV=production
|
||||
# npm install
|
||||
ADD package*.json /src/
|
||||
WORKDIR /src
|
||||
RUN npm i
|
||||
|
||||
# build
|
||||
ADD . /src
|
||||
RUN npm run build
|
||||
|
||||
# clean
|
||||
RUN npm prune --production
|
||||
|
||||
# move
|
||||
RUN rm -rf /app \
|
||||
&& mv dist /app \
|
||||
&& mv node_modules /app/ \
|
||||
&& rm -rf /src
|
||||
|
||||
# ENV
|
||||
ENV NODE_ENV production
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
# 指定預設/工作資料夾
|
||||
WORKDIR /app
|
||||
|
||||
# 只copy package.json檔案
|
||||
COPY ["package.json", "./"]
|
||||
|
||||
# 安裝dependencies
|
||||
# If you are building your code for production
|
||||
# RUN npm ci --only=production
|
||||
RUN npm install
|
||||
|
||||
# copy其餘目錄及檔案
|
||||
COPY . .
|
||||
|
||||
# 指定啟動container後執行命令
|
||||
CMD [ "npm", "start" ]
|
||||
CMD node index.js
|
31
README.md
Normal file
31
README.md
Normal file
@ -0,0 +1,31 @@
|
||||
# TSRPC Server
|
||||
|
||||
## Usage
|
||||
### Local dev server
|
||||
|
||||
Dev server would restart automatically when code changed.
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Build
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Generate API document
|
||||
|
||||
Generate API document in swagger/openapi and markdown format.
|
||||
|
||||
```shell
|
||||
npm run doc
|
||||
```
|
||||
|
||||
### Run unit Test
|
||||
Execute `npm run dev` first, then execute:
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
---
|
164
docs/openapi.json
Normal file
164
docs/openapi.json
Normal file
@ -0,0 +1,164 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "TSRPC Open API",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"/Send": {
|
||||
"post": {
|
||||
"operationId": "Send",
|
||||
"requestBody": {
|
||||
"description": "Req<Send>",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PtlSend_ReqSend"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"description": "ApiReturn<ResSend>",
|
||||
"properties": {
|
||||
"isSucc": {
|
||||
"type": "boolean",
|
||||
"enum": [
|
||||
true
|
||||
],
|
||||
"default": true
|
||||
},
|
||||
"res": {
|
||||
"$ref": "#/components/schemas/PtlSend_ResSend"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Error",
|
||||
"$ref": "#/components/responses/error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"MsgChat_MsgChat": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content",
|
||||
"time"
|
||||
]
|
||||
},
|
||||
"PtlSend_ReqSend": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
]
|
||||
},
|
||||
"PtlSend_ResSend": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"time": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"time"
|
||||
]
|
||||
},
|
||||
"?bson_ObjectID": {
|
||||
"type": "string"
|
||||
},
|
||||
"?bson_ObjectId": {
|
||||
"type": "string"
|
||||
},
|
||||
"?mongodb_ObjectID": {
|
||||
"type": "string"
|
||||
},
|
||||
"?mongodb_ObjectId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"error": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"title": "API 错误",
|
||||
"description": "业务错误(ApiError)返回 HTTP 状态码 200,其它错误返回 HTTP 状态码 500",
|
||||
"properties": {
|
||||
"isSucc": {
|
||||
"type": "boolean",
|
||||
"enum": [
|
||||
false
|
||||
],
|
||||
"default": false
|
||||
},
|
||||
"err": {
|
||||
"type": "object",
|
||||
"description": "TsrpcError",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ApiError",
|
||||
"NetworkError",
|
||||
"ServerError",
|
||||
"ClientError"
|
||||
]
|
||||
},
|
||||
"code": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"type"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
docs/tsapi.md
Normal file
34
docs/tsapi.md
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
# TSRPC API 接口文档
|
||||
|
||||
## 通用说明
|
||||
|
||||
- 所有请求方法均为 `POST`
|
||||
- 所有请求均需加入以下 Header :
|
||||
- `Content-Type: application/json`
|
||||
|
||||
## 目录
|
||||
|
||||
- [Send](#/Send)
|
||||
|
||||
---
|
||||
|
||||
## Send <a id="/Send"></a>
|
||||
|
||||
**路径**
|
||||
- POST `/Send`
|
||||
|
||||
**请求**
|
||||
```ts
|
||||
interface ReqSend {
|
||||
content: string
|
||||
}
|
||||
```
|
||||
|
||||
**响应**
|
||||
```ts
|
||||
interface ResSend {
|
||||
time: /*datetime*/ string
|
||||
}
|
||||
```
|
||||
|
48
package.json
48
package.json
@ -1,35 +1,29 @@
|
||||
{
|
||||
"name": "line-bot-ts",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/app.ts",
|
||||
"name": "guesswhoiams-backend",
|
||||
"version": "0.1.0",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "nodemon src/app.ts",
|
||||
"test": "nodemon src/app.ts",
|
||||
"dev": "nodemon --exec \"node --require ts-node/register --inspect=192.168.5.36:9229 src/app.ts\"",
|
||||
"build": "tsc --project ./"
|
||||
"dev": "tsrpc-cli dev",
|
||||
"debug": "nodemon --exec \"node --require ts-node/register --inspect=192.168.5.36:9229 src/index.ts\"",
|
||||
"build": "tsrpc-cli build",
|
||||
"doc": "tsrpc-cli doc",
|
||||
"test": "mocha test/**/*.test.ts",
|
||||
"proto": "tsrpc-cli proto",
|
||||
"sync": "tsrpc-cli sync",
|
||||
"api": "tsrpc-cli api"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/dateformat": "^5.0.0",
|
||||
"@types/express": "^4.17.15",
|
||||
"@types/mysql": "^2.15.21",
|
||||
"@types/node": "^18.11.18",
|
||||
"typescript": "^4.9.4"
|
||||
"@types/mocha": "^8.2.3",
|
||||
"@types/node": "^15.14.9",
|
||||
"mocha": "^9.2.2",
|
||||
"onchange": "^7.1.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsrpc-cli": "^2.4.5",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@line/bot-sdk": "^7.5.2",
|
||||
"@types/ws": "^8.5.5",
|
||||
"dateformat": "^4.5.1",
|
||||
"dayjs": "^1.11.7",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"fs": "^0.0.1-security",
|
||||
"mysql": "^2.18.1",
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-node": "^10.9.1",
|
||||
"ws": "^8.13.0",
|
||||
"xmlhttprequest": "^1.8.0"
|
||||
"dayjs": "^1.11.9",
|
||||
"tsrpc": "^3.4.12"
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
|
||||
import _ws from "ws"
|
||||
import { Encoding } from "../Engine/CatanEngine/CSharp/System/Text/Encoding"
|
||||
import { INetResponse } from "../Engine/CatanEngine/NetManagerV2/Core/INetResponse"
|
||||
import WebSocketServerClass from "../NetManager/WebSocketServerClass"
|
||||
|
||||
/**
|
||||
* Client
|
||||
*/
|
||||
export default class Client {
|
||||
|
||||
//#region private
|
||||
|
||||
private ws: _ws = undefined
|
||||
private clientCount: number = undefined
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(ws: _ws, clientCount: number) {
|
||||
this.ws = ws
|
||||
this.clientCount = clientCount
|
||||
|
||||
// 當收到client消息時
|
||||
ws.on('message', this.onMessage.bind(this))
|
||||
// 當連線關閉
|
||||
ws.on('close', this.onClose.bind(this))
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Custom
|
||||
|
||||
private onMessage(buffer: _ws.RawData): void {
|
||||
// 收回來是 Buffer 格式、需轉成字串
|
||||
const dataStr: string = "[" + buffer.toString().split("[").slice(1).join("[")
|
||||
const json = JSON.parse(dataStr)
|
||||
const method = <string>json[0]
|
||||
let status = 0
|
||||
const data = json[1]
|
||||
const resp = {
|
||||
Method: method,
|
||||
Status: status,
|
||||
Data: data,
|
||||
IsValid: method && status === 0,
|
||||
WS: this
|
||||
}
|
||||
|
||||
if (true) {
|
||||
if (data) {
|
||||
console.debug(`[RPC] 收到server呼叫:(${resp.WS.clientCount}): ${resp.Method}(${JSON.stringify(resp.Data)})`)
|
||||
} else {
|
||||
console.debug(`[RPC] 收到server呼叫:(${resp.WS.clientCount}): ${resp.Method}()`)
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketServerClass.Instance.OnDataReceived.DispatchCallback(resp)
|
||||
|
||||
// /// 發送消息給client
|
||||
// this.SendClient(data)
|
||||
// WebSocketServerClass.Instance.SendAllClient(data)
|
||||
}
|
||||
|
||||
private onClose(): void {
|
||||
console.log(`Client_${this.clientCount} Close connected`)
|
||||
}
|
||||
|
||||
/** 發送給client */
|
||||
public SendClient(req: INetResponse<any>): void {
|
||||
const status = 0
|
||||
const json: any[] = [req.Method]
|
||||
//@ts-ignore
|
||||
if (req.Data != null && req.Data != undefined && req.Data != NaN) {
|
||||
json[1] = [status, req.Data]
|
||||
}
|
||||
|
||||
if (true) {
|
||||
//@ts-ignore
|
||||
if (req.Data != null && req.Data != undefined && req.Data != NaN) {
|
||||
console.log(`[RPC] 傳送client資料:(${this.clientCount}): ${req.Method}(${JSON.stringify(req.Data)})`)
|
||||
} else {
|
||||
console.log(`[RPC] 傳送client資料:(${this.clientCount}): ${req.Method}()`)
|
||||
}
|
||||
}
|
||||
|
||||
const str = JSON.stringify(json)
|
||||
if (str.length > 65535) {
|
||||
throw new Error('要傳的資料太大囉')
|
||||
}
|
||||
|
||||
const strary = Encoding.UTF8.GetBytes(str)
|
||||
const buffer = new Uint8Array(4 + strary.byteLength)
|
||||
const u16ary = new Uint16Array(buffer.buffer, 0, 3)
|
||||
u16ary[0] = strary.byteLength
|
||||
buffer[3] = 0x01
|
||||
buffer.set(strary, 4)
|
||||
|
||||
this.ws.send(buffer)
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import { INetResponse } from "../Engine/CatanEngine/NetManagerV2/Core/INetResponse";
|
||||
import Lobby from "../Lobby/Lobby";
|
||||
import WebSocketServerClass from "../NetManager/WebSocketServerClass";
|
||||
|
||||
export default class MainControlData {
|
||||
constructor() {
|
||||
WebSocketServerClass.Instance.OnDataReceived.AddCallback(this._serverData, this)
|
||||
}
|
||||
|
||||
/** SERVER主動通知 */
|
||||
private _serverData(req: INetResponse<any>): void {
|
||||
if (req.IsValid) {
|
||||
switch (req.Method) {
|
||||
case "lobby.list":
|
||||
Lobby.List(req);
|
||||
break
|
||||
case "lobby.create":
|
||||
Lobby.Create(req);
|
||||
break
|
||||
default:
|
||||
// if (GameMain.Instance && GameMain.Instance.node && GameMain.Instance.node.parent) {
|
||||
// GameMain.Instance.SettingBase.OnNetDataReceived(resp)
|
||||
// }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import { LobbyCreateRequest, LobbyListRequest } from "../define/Request/LobbyRequest";
|
||||
import { INetResponse } from "../Engine/CatanEngine/NetManagerV2/Core/INetResponse";
|
||||
import Room from "../Room/Room";
|
||||
|
||||
/**
|
||||
* Lobby
|
||||
*/
|
||||
export default class Lobby {
|
||||
|
||||
//#region private
|
||||
|
||||
private static list: Room[] = []
|
||||
private static serialNumber: number = 0
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Custom
|
||||
|
||||
/** GetList */
|
||||
public static List(req: INetResponse<any>): void {
|
||||
const data = []
|
||||
for (let i = 0; i < this.list.length; i++) {
|
||||
const room = this.list[i];
|
||||
data.push(room.SerialNumber)
|
||||
}
|
||||
const resp: LobbyListRequest = new LobbyListRequest(data, 0)
|
||||
req.WS.SendClient(resp)
|
||||
}
|
||||
|
||||
/** Create */
|
||||
public static Create(req: INetResponse<any>): void {
|
||||
const room: Room = new Room(Lobby.serialNumber, req.WS)
|
||||
Lobby.serialNumber++;
|
||||
this.list.push(room)
|
||||
const resp: LobbyCreateRequest = new LobbyCreateRequest()
|
||||
req.WS.SendClient(resp)
|
||||
}
|
||||
|
||||
/** Join */
|
||||
public static Join(req: INetResponse<any>): void {
|
||||
//
|
||||
}
|
||||
|
||||
/** Exit */
|
||||
public static Exit(req: INetResponse<any>): void {
|
||||
//
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// export class MainControl {
|
||||
// //#region 網路相關
|
||||
|
||||
// /**連線(目前沒有重連機制) */
|
||||
// public * ConnectAsync() {
|
||||
// if (NetManager.IsConnected) {
|
||||
// return
|
||||
// }
|
||||
// this._conn = new NetConnector(BusinessTypeSetting.UseHost, BusinessTypeSetting.UsePort/*, this._realIp*/)
|
||||
// this._conn.OnDataReceived.AddCallback(this._onNetDataReceived, this)
|
||||
// this._conn.OnDisconnected.AddCallback(this._onNetDisconnected, this)
|
||||
// this._conn.OnLoadUIMask.AddCallback(this._oOnLoadUIMask, this)
|
||||
// NetManager.Initialize(this._conn)
|
||||
// cc.log("[socket] connecting...")
|
||||
// // 同個connector要再次連線, 可以不用叫CasinoNetManager.Initialize(), 但要先叫CasinoNetManager.Disconnect()
|
||||
// yield NetManager.ConnectAsync()
|
||||
// }
|
||||
|
||||
// //#endregion
|
||||
|
||||
// }
|
@ -1,79 +0,0 @@
|
||||
import express from "express"
|
||||
import fs from "fs"
|
||||
import _ws from "ws"
|
||||
import Client from "../Client/Client"
|
||||
import { Action } from "../Engine/CatanEngine/CSharp/System/Action"
|
||||
import { INetResponse } from "../Engine/CatanEngine/NetManagerV2/Core/INetResponse"
|
||||
import BaseSingleton from "../Engine/Utils/Singleton/BaseSingleton"
|
||||
const SocketServer: typeof _ws.Server = _ws.Server
|
||||
|
||||
/**
|
||||
* WebSocketServer
|
||||
*/
|
||||
export default class WebSocketServerClass extends BaseSingleton<WebSocketServerClass>() {
|
||||
|
||||
readonly OnDataReceived: Action<INetResponse<any>> = new Action<INetResponse<any>>()
|
||||
|
||||
//#region private
|
||||
|
||||
private wss: _ws.Server = undefined
|
||||
private clientCount: number = 0
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor() {
|
||||
super()
|
||||
//讀取憑證及金鑰
|
||||
const prikey: string = fs.readFileSync("./certificate/RSA-privkey.pem", "utf8")
|
||||
const cert: string = fs.readFileSync("./certificate/RSA-cert.pem", "utf8")
|
||||
const cafile: string = fs.readFileSync("./certificate/RSA-chain.pem", "utf-8")
|
||||
|
||||
//建立憑證及金鑰
|
||||
const credentials: Object = {
|
||||
key: prikey,
|
||||
cert: cert,
|
||||
ca: cafile
|
||||
}
|
||||
|
||||
// 用於辨識Line Channel的資訊
|
||||
const config: any = {
|
||||
channelSecret: process.env.channelSecret,
|
||||
channelAccessToken: process.env.channelAccessToken || ""
|
||||
}
|
||||
|
||||
const port: number = +process.env.PORT || 3000
|
||||
const server = express().listen(port, () => {
|
||||
console.log(`Listening on ${port}`)
|
||||
})
|
||||
//將 express 交給 SocketServer 開啟 WebSocket 的服務
|
||||
this.wss = new SocketServer({ server })
|
||||
//當有 client 連線成功時
|
||||
this.wss.on('connection', this.onConnection.bind(this))
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Custom
|
||||
|
||||
private onConnection(ws: _ws): void {
|
||||
const clientNum: number = this.clientCount
|
||||
console.log(`Client_${clientNum} connected`)
|
||||
new Client(ws, clientNum)
|
||||
this.clientCount++
|
||||
}
|
||||
|
||||
/** 發送給所有client */
|
||||
public SendAllClient(data: any): void {
|
||||
let clients = this.wss.clients //取得所有連接中的 client
|
||||
clients.forEach(client => {
|
||||
client.send(data) // 發送至每個 client
|
||||
})
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
import mysql from "mysql"
|
||||
import Tools from "./Tools"
|
||||
|
||||
/**
|
||||
* DBTools
|
||||
*/
|
||||
export default class DBTools {
|
||||
|
||||
//#region Custom
|
||||
|
||||
public static async Query(query: string): Promise<any> {
|
||||
const conn: mysql.Connection = this.connect()
|
||||
|
||||
let resp: any = null
|
||||
let run: boolean = true
|
||||
conn.query(query, function (err: mysql.MysqlError, rows: any, fields: mysql.FieldInfo[]): void {
|
||||
if (err) {
|
||||
console.error(`${query} Error: \n${err.message}`)
|
||||
run = false
|
||||
}
|
||||
resp = rows
|
||||
run = false
|
||||
})
|
||||
while (run) {
|
||||
await Tools.Sleep(100)
|
||||
}
|
||||
conn.end()
|
||||
return resp
|
||||
}
|
||||
|
||||
private static connect(): mysql.Connection {
|
||||
const conn: mysql.Connection = mysql.createConnection({
|
||||
host: process.env.DB_HOST,
|
||||
port: +process.env.DB_PORT,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE
|
||||
})
|
||||
conn.connect()
|
||||
return conn
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Tools
|
||||
*/
|
||||
export default class Tools {
|
||||
|
||||
//#region Custom
|
||||
|
||||
public static Sleep(ms: number): Promise<any> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
24
src/api/ApiAccountLogin.ts
Normal file
24
src/api/ApiAccountLogin.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { ApiCall, BaseConnection } from "tsrpc";
|
||||
import Client from "../component/Client/Client";
|
||||
import User from "../component/Client/User";
|
||||
import Lobby from "../component/Lobby/Lobby";
|
||||
import { ReqAccountLogin, ResAccountLogin } from "../shared/protocols/PtlAccountLogin";
|
||||
|
||||
export default async function (call: ApiCall<ReqAccountLogin, ResAccountLogin>) {
|
||||
// Error
|
||||
if (!call.req.name) {
|
||||
call.error('Name is empty')
|
||||
return;
|
||||
}
|
||||
|
||||
// Success
|
||||
const { sn, req } = call
|
||||
const { name } = req
|
||||
const conn: BaseConnection<any> = call.conn
|
||||
console.log(`name: ${name} is Login`)
|
||||
const user = new User(name)
|
||||
const client = new Client(conn, sn)
|
||||
client.setUser(user)
|
||||
Lobby.AddClient(client)
|
||||
call.succ(0)
|
||||
}
|
12
src/api/ApiLobbyList.ts
Normal file
12
src/api/ApiLobbyList.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ApiCall } from "tsrpc";
|
||||
import Lobby from "../component/Lobby/Lobby";
|
||||
import { ReqLobbyList, ResLobbyList } from "../shared/protocols/PtlLobbyList";
|
||||
|
||||
export default async function (call: ApiCall<ReqLobbyList, ResLobbyList>) {
|
||||
const data: any[] = []
|
||||
for (let i = 0; i < Lobby.Room.length; i++) {
|
||||
const room = Lobby.Room[i]
|
||||
data.push(room.SerialNumber)
|
||||
}
|
||||
call.succ(data)
|
||||
}
|
26
src/api/ApiSend.ts
Normal file
26
src/api/ApiSend.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { ApiCall } from "tsrpc";
|
||||
import { server } from "..";
|
||||
import { ReqSend, ResSend } from "../shared/protocols/PtlSend";
|
||||
|
||||
// This is a demo code file
|
||||
// Feel free to delete it
|
||||
|
||||
export default async function (call: ApiCall<ReqSend, ResSend>) {
|
||||
// Error
|
||||
if (call.req.content.length === 0) {
|
||||
call.error('Content is empty')
|
||||
return;
|
||||
}
|
||||
|
||||
// Success
|
||||
let time = new Date();
|
||||
call.succ({
|
||||
time: time
|
||||
});
|
||||
|
||||
// Broadcast
|
||||
server.broadcastMsg('Chat', {
|
||||
content: call.req.content,
|
||||
time: time
|
||||
})
|
||||
}
|
18
src/app.ts
18
src/app.ts
@ -1,18 +0,0 @@
|
||||
// 背景執行 forever start -c ts-node -a -l line-bot-ts.log src/app.ts
|
||||
// 重新背景執行 forever restart -a -l line-bot-ts.log src/app.ts
|
||||
// 監聽檔案變化 "npm start"
|
||||
// 連線Debug "npm run dev"
|
||||
|
||||
import dayjs from "dayjs"
|
||||
import "dayjs/locale/zh-tw"
|
||||
import dotenv from "dotenv"
|
||||
import MainControlData from "./DataReceived/MainControlData"
|
||||
import "./Engine/CatanEngine/CSharp/String"
|
||||
import "./Engine/Utils/CCExtensions/ArrayExtension"
|
||||
import "./Engine/Utils/CCExtensions/NumberExtension"
|
||||
import WebSocketServerClass from "./NetManager/WebSocketServerClass"
|
||||
|
||||
dayjs.locale("zh-tw")
|
||||
dotenv.config()
|
||||
new WebSocketServerClass()
|
||||
new MainControlData()
|
128
src/component/Client/Client.ts
Normal file
128
src/component/Client/Client.ts
Normal file
@ -0,0 +1,128 @@
|
||||
|
||||
import { BaseConnection } from "tsrpc"
|
||||
import User from "./User"
|
||||
|
||||
/**
|
||||
* Client
|
||||
*/
|
||||
export default class Client {
|
||||
|
||||
//#region private
|
||||
|
||||
private conn: BaseConnection<any> = undefined
|
||||
private ws: any = undefined
|
||||
private sn: number = undefined
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region get set
|
||||
|
||||
public get User(): User {
|
||||
return this.user
|
||||
}
|
||||
|
||||
private user: User = undefined
|
||||
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(conn: BaseConnection<any>, sn: number) {
|
||||
this.conn = conn
|
||||
this.ws = conn["ws"]
|
||||
this.sn = sn
|
||||
|
||||
// // 當收到client消息時
|
||||
// ws.on('message', this.onMessage.bind(this))
|
||||
// // 當連線關閉
|
||||
// ws.on('close', this.onClose.bind(this))
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Custom
|
||||
|
||||
/**
|
||||
* setUser
|
||||
*/
|
||||
public setUser(user: User) {
|
||||
this.user = user
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Server
|
||||
|
||||
// private onMessage(buffer: _ws.RawData): void {
|
||||
// // 收回來是 Buffer 格式、需轉成字串
|
||||
// const dataStr: string = "[" + buffer.toString().split("[").slice(1).join("[")
|
||||
// const json = JSON.parse(dataStr)
|
||||
// const method = <string>json[0]
|
||||
// let status = 0
|
||||
// const data = json[1]
|
||||
// const resp = {
|
||||
// Method: method,
|
||||
// Status: status,
|
||||
// Data: data,
|
||||
// IsValid: method && status === 0,
|
||||
// WS: this
|
||||
// }
|
||||
|
||||
// if (true) {
|
||||
// if (data) {
|
||||
// console.debug(`[RPC] 收到server呼叫:(${resp.WS.clientCount}): ${resp.Method}(${JSON.stringify(resp.Data)})`)
|
||||
// } else {
|
||||
// console.debug(`[RPC] 收到server呼叫:(${resp.WS.clientCount}): ${resp.Method}()`)
|
||||
// }
|
||||
// }
|
||||
|
||||
// WebSocketServerClass.Instance.OnDataReceived.DispatchCallback(resp)
|
||||
|
||||
// // /// 發送消息給client
|
||||
// // this.SendClient(data)
|
||||
// // WebSocketServerClass.Instance.SendAllClient(data)
|
||||
// }
|
||||
|
||||
// private onClose(): void {
|
||||
// console.log(`Client_${this.clientCount} Close connected`)
|
||||
// }
|
||||
|
||||
// /** 發送給client */
|
||||
// public SendClient(req: INetResponse<any>): void {
|
||||
// const status = 0
|
||||
// const json: any[] = [req.Method]
|
||||
// //@ts-ignore
|
||||
// if (req.Data != null && req.Data != undefined && req.Data != NaN) {
|
||||
// json[1] = [status, req.Data]
|
||||
// }
|
||||
|
||||
// if (true) {
|
||||
// //@ts-ignore
|
||||
// if (req.Data != null && req.Data != undefined && req.Data != NaN) {
|
||||
// console.log(`[RPC] 傳送client資料:(${this.clientCount}): ${req.Method}(${JSON.stringify(req.Data)})`)
|
||||
// } else {
|
||||
// console.log(`[RPC] 傳送client資料:(${this.clientCount}): ${req.Method}()`)
|
||||
// }
|
||||
// }
|
||||
|
||||
// const str = JSON.stringify(json)
|
||||
// if (str.length > 65535) {
|
||||
// throw new Error('要傳的資料太大囉')
|
||||
// }
|
||||
|
||||
// const strary = Encoding.UTF8.GetBytes(str)
|
||||
// const buffer = new Uint8Array(4 + strary.byteLength)
|
||||
// const u16ary = new Uint16Array(buffer.buffer, 0, 3)
|
||||
// u16ary[0] = strary.byteLength
|
||||
// buffer[3] = 0x01
|
||||
// buffer.set(strary, 4)
|
||||
|
||||
// this.ws.send(buffer)
|
||||
// }
|
||||
|
||||
//#endregion
|
||||
}
|
29
src/component/Client/User.ts
Normal file
29
src/component/Client/User.ts
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
/**
|
||||
* User
|
||||
*/
|
||||
export default class User {
|
||||
|
||||
//#region get set
|
||||
|
||||
public get Name(): string {
|
||||
return this.name
|
||||
}
|
||||
|
||||
private name: string = undefined
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Custom
|
||||
|
||||
//#endregion
|
||||
}
|
77
src/component/Lobby/Lobby.ts
Normal file
77
src/component/Lobby/Lobby.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import Client from "../Client/Client";
|
||||
import Room from "../Room/Room";
|
||||
|
||||
/**
|
||||
* Lobby
|
||||
*/
|
||||
export default class Lobby {
|
||||
|
||||
//#region private
|
||||
|
||||
private static clients: Client[] = []
|
||||
private static serialNumber: number = 0
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region get set
|
||||
|
||||
public static get Room(): Room[] { return this.room }
|
||||
private static room: Room[] = []
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Custom
|
||||
|
||||
/** AddClient */
|
||||
public static AddClient(client: Client): void {
|
||||
this.clients.push(client)
|
||||
}
|
||||
|
||||
// /** List */
|
||||
// public static List(req: INetResponse<RpcLobbyListRequest>): void {
|
||||
// const data = []
|
||||
// for (let i = 0; i < this.list.length; i++) {
|
||||
// const room = this.list[i]
|
||||
// data.push(room.SerialNumber)
|
||||
// }
|
||||
// const resp: LobbyListRequest = new LobbyListRequest(data, 0)
|
||||
// req.WS.SendClient(resp)
|
||||
// }
|
||||
|
||||
// /** Create */
|
||||
// public static Create(req: INetResponse<RpcLobbyCreateRequest>): void {
|
||||
// const room: Room = new Room(Lobby.serialNumber, req.WS)
|
||||
// Lobby.serialNumber++
|
||||
// this.list.push(room)
|
||||
// const resp: LobbyCreateRequest = new LobbyCreateRequest()
|
||||
// req.WS.SendClient(resp)
|
||||
// }
|
||||
|
||||
// /** Join */
|
||||
// public static Join(req: INetResponse<RpcLobbyJoinRequest>): void {
|
||||
// const serialNumber: number = req.Data
|
||||
// for (let i = 0; i < this.list.length; i++) {
|
||||
// const room = this.list[i]
|
||||
// if (room.SerialNumber === serialNumber) {
|
||||
// room.Join(req.WS)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// /** Exit */
|
||||
// public static Exit(req: INetResponse<RpcLobbyExitRequest>): void {
|
||||
// const serialNumber: number = req.Data
|
||||
// for (let i = 0; i < this.list.length; i++) {
|
||||
// const room = this.list[i]
|
||||
// if (room.SerialNumber === serialNumber) {
|
||||
// this.list.splice(i, 1)
|
||||
// // room.Exit()
|
||||
// // room = null
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
//#endregion
|
||||
}
|
@ -31,5 +31,13 @@ export default class Room {
|
||||
|
||||
//#region Custom
|
||||
|
||||
/** Join */
|
||||
public Join(ws: Client): void {
|
||||
this.wsArr.forEach(otherWS => {
|
||||
// otherWS.SendClient()
|
||||
})
|
||||
this.wsArr.push(ws)
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import { NetRequest } from "../../Engine/CatanEngine/NetManagerV2/NetRequest";
|
||||
|
||||
// #region Request
|
||||
|
||||
export type RpcExampleCodeRequest = null
|
||||
export type RpcExampleCodeResponse = ExampleCodeData[]
|
||||
|
||||
export class ExampleCodeRequest extends NetRequest<RpcExampleCodeRequest, RpcExampleCodeResponse> {
|
||||
get Method(): string {
|
||||
return "example.code";
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Type
|
||||
|
||||
export type ExampleCodeData = [
|
||||
id: number,
|
||||
title: number,
|
||||
content: number,
|
||||
time: number,
|
||||
];
|
||||
|
||||
// #endregion
|
@ -1,23 +0,0 @@
|
||||
import { NetResponse } from "../../Engine/CatanEngine/NetManagerV2/NetResponse"
|
||||
|
||||
// #region Request
|
||||
|
||||
export type RpcLobbyListRequest = any[]
|
||||
export class LobbyListRequest extends NetResponse {
|
||||
protected data: RpcLobbyListRequest
|
||||
protected method: string = "lobby.list"
|
||||
constructor(data: RpcLobbyListRequest = undefined, status: number = 0) { super(data, status) }
|
||||
}
|
||||
|
||||
export type RpcLobbyCreateRequest = undefined
|
||||
export class LobbyCreateRequest extends NetResponse {
|
||||
protected data: RpcLobbyListRequest
|
||||
protected method: string = "lobby.create"
|
||||
constructor(data: RpcLobbyCreateRequest = undefined, status: number = 0) { super(data, status) }
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Type
|
||||
|
||||
// #endregion
|
34
src/index.ts
Normal file
34
src/index.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/zh-tw";
|
||||
import * as path from "path";
|
||||
import { WsServer } from "tsrpc";
|
||||
import { BaseEnumerator } from "./Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator";
|
||||
import "./Engine/CatanEngine/CSharp/String";
|
||||
import "./Engine/Utils/CCExtensions/ArrayExtension";
|
||||
import "./Engine/Utils/CCExtensions/NumberExtension";
|
||||
import { serviceProto } from './shared/protocols/serviceProto';
|
||||
|
||||
BaseEnumerator.Init();
|
||||
dayjs.locale("zh-tw")
|
||||
|
||||
// Create the Server
|
||||
export const server = new WsServer(serviceProto, {
|
||||
port: 3003,
|
||||
// Remove this to use binary mode (remove from the client too)
|
||||
json: true
|
||||
});
|
||||
|
||||
// Initialize before server start
|
||||
async function init() {
|
||||
await server.autoImplementApi(path.resolve(__dirname, 'api'));
|
||||
|
||||
// TODO
|
||||
// Prepare something... (e.g. connect the db)
|
||||
};
|
||||
|
||||
// Entry function
|
||||
async function main() {
|
||||
await init();
|
||||
await server.start();
|
||||
}
|
||||
main();
|
1
src/shared/protocols
Submodule
1
src/shared/protocols
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 3aab251c77cbd76451676b0cab3283cf345b0043
|
53
test/api/ApiSend.test.ts
Normal file
53
test/api/ApiSend.test.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import assert from 'assert';
|
||||
import { WsClient } from 'tsrpc';
|
||||
import { serviceProto } from '../../src/shared/protocols/serviceProto';
|
||||
|
||||
// 1. EXECUTE `npm run dev` TO START A LOCAL DEV SERVER
|
||||
// 2. EXECUTE `npm test` TO START UNIT TEST
|
||||
|
||||
describe('ApiSend', function () {
|
||||
let client = new WsClient(serviceProto, {
|
||||
server: 'ws://127.0.0.1:3003',
|
||||
json: true,
|
||||
logger: console
|
||||
});
|
||||
|
||||
// 連線
|
||||
before(async function () {
|
||||
let res = await client.connect();
|
||||
assert.strictEqual(res.isSucc, true, 'Failed to connect to server, have you executed `npm run dev` already?');
|
||||
})
|
||||
|
||||
it('AccountLogin', async function () {
|
||||
let ret = await client.callApi('AccountLogin', {
|
||||
name: 'Test'
|
||||
});
|
||||
assert.ok(ret.isSucc)
|
||||
});
|
||||
|
||||
it('LobbyList', async function () {
|
||||
let ret = await client.callApi('LobbyList', null);
|
||||
assert.ok(ret.isSucc)
|
||||
});
|
||||
|
||||
// it('Success', async function () {
|
||||
// let ret = await client.callApi('Send', {
|
||||
// content: 'Test'
|
||||
// });
|
||||
// assert.ok(ret.isSucc)
|
||||
// });
|
||||
|
||||
// it('Check content is empty', async function () {
|
||||
// let ret = await client.callApi('Send', {
|
||||
// content: ''
|
||||
// });
|
||||
// assert.deepStrictEqual(ret, {
|
||||
// isSucc: false,
|
||||
// err: new TsrpcError('Content is empty')
|
||||
// });
|
||||
// })
|
||||
|
||||
after(async function () {
|
||||
await client.disconnect();
|
||||
})
|
||||
})
|
19
test/tsconfig.json
Normal file
19
test/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2018"
|
||||
],
|
||||
"module": "commonjs",
|
||||
"target": "es2018",
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": [
|
||||
".",
|
||||
"../src"
|
||||
]
|
||||
}
|
@ -1,16 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"lib": [
|
||||
"es2015",
|
||||
"es2017",
|
||||
"dom"
|
||||
"es2018"
|
||||
],
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
"strict": false, /* Enable all strict type-checking options. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
||||
"outDir": "dist" // 將編譯過後的js檔放到dist資料夾中
|
||||
}
|
||||
"module": "commonjs",
|
||||
"target": "es2018",
|
||||
"outDir": "dist",
|
||||
"strict": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
// "paths": {
|
||||
// "@/*": [
|
||||
// "./src/*"
|
||||
// ]
|
||||
// }
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
38
tsrpc.config.ts
Normal file
38
tsrpc.config.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import type { TsrpcConfig } from 'tsrpc-cli';
|
||||
|
||||
export default <TsrpcConfig>{
|
||||
// Generate ServiceProto
|
||||
proto: [
|
||||
{
|
||||
ptlDir: 'src/shared/protocols', // Protocol dir
|
||||
output: 'src/shared/protocols/serviceProto.ts', // Path for generated ServiceProto
|
||||
apiDir: 'src/api', // API dir
|
||||
docDir: 'docs', // API documents dir
|
||||
ptlTemplate: { baseFile: 'src/shared/protocols/base.ts' },
|
||||
// msgTemplate: { baseFile: 'src/shared/protocols/base.ts' },
|
||||
}
|
||||
],
|
||||
// Sync shared code
|
||||
sync: [
|
||||
{
|
||||
from: 'src/shared',
|
||||
to: '../frontend/src/shared',
|
||||
type: 'symlink' // Change this to 'copy' if your environment not support symlink
|
||||
}
|
||||
],
|
||||
// Dev server
|
||||
dev: {
|
||||
autoProto: true, // Auto regenerate proto
|
||||
autoSync: true, // Auto sync when file changed
|
||||
autoApi: true, // Auto create API when ServiceProto updated
|
||||
watch: 'src', // Restart dev server when these files changed
|
||||
entry: 'src/index.ts', // Dev server command: node -r ts-node/register {entry}
|
||||
},
|
||||
// Build config
|
||||
build: {
|
||||
autoProto: true, // Auto generate proto before build
|
||||
autoSync: true, // Auto sync before build
|
||||
autoApi: true, // Auto generate API before build
|
||||
outDir: 'dist', // Clean this dir before build
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user