parent
d9aed3c8ee
commit
fe96f5290b
@ -1,143 +1,58 @@ |
||||
# ---> Node |
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
lerna-debug.log* |
||||
.pnpm-debug.log* |
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html) |
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
||||
|
||||
# Runtime data |
||||
pids |
||||
*.pid |
||||
*.seed |
||||
*.pid.lock |
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover |
||||
lib-cov |
||||
|
||||
# Coverage directory used by tools like istanbul |
||||
coverage |
||||
*.lcov |
||||
|
||||
# nyc test coverage |
||||
.nyc_output |
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) |
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) |
||||
.grunt |
||||
|
||||
# Bower dependency directory (https://bower.io/) |
||||
bower_components |
||||
|
||||
# node-waf configuration |
||||
.lock-wscript |
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html) |
||||
# Compiled binary addons (http://nodejs.org/api/addons.html) |
||||
build/Release |
||||
|
||||
# Dependency directories |
||||
node_modules/ |
||||
jspm_packages/ |
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/) |
||||
web_modules/ |
||||
|
||||
# TypeScript cache |
||||
*.tsbuildinfo |
||||
node_modules |
||||
jspm_packages |
||||
|
||||
# Optional npm cache directory |
||||
.npm |
||||
|
||||
# Optional eslint cache |
||||
.eslintcache |
||||
|
||||
# Optional stylelint cache |
||||
.stylelintcache |
||||
|
||||
# Microbundle cache |
||||
.rpt2_cache/ |
||||
.rts2_cache_cjs/ |
||||
.rts2_cache_es/ |
||||
.rts2_cache_umd/ |
||||
|
||||
# Optional REPL history |
||||
.node_repl_history |
||||
|
||||
# Output of 'npm pack' |
||||
*.tgz |
||||
|
||||
# Yarn Integrity file |
||||
.yarn-integrity |
||||
|
||||
# dotenv environment variable files |
||||
.env |
||||
.env.development.local |
||||
.env.test.local |
||||
.env.production.local |
||||
.env.local |
||||
|
||||
# parcel-bundler cache (https://parceljs.org/) |
||||
.cache |
||||
.parcel-cache |
||||
|
||||
# Next.js build output |
||||
.next |
||||
out |
||||
|
||||
# Nuxt.js build / generate output |
||||
.nuxt |
||||
dist |
||||
|
||||
# Gatsby files |
||||
.cache/ |
||||
# Comment in the public line in if your project uses Gatsby and not Next.js |
||||
# https://nextjs.org/blog/next-9-1#public-directory-support |
||||
# public |
||||
|
||||
# vuepress build output |
||||
.vuepress/dist |
||||
|
||||
# vuepress v2.x temp and cache directory |
||||
.temp |
||||
.cache |
||||
|
||||
# Docusaurus cache and generated files |
||||
.docusaurus |
||||
|
||||
# Serverless directories |
||||
.serverless/ |
||||
|
||||
# FuseBox cache |
||||
.fusebox/ |
||||
|
||||
# DynamoDB Local files |
||||
.dynamodb/ |
||||
|
||||
# TernJS port file |
||||
.tern-port |
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions |
||||
.vscode-test |
||||
# 0x |
||||
profile-* |
||||
|
||||
# yarn v2 |
||||
.yarn/cache |
||||
.yarn/unplugged |
||||
.yarn/build-state.yml |
||||
.yarn/install-state.gz |
||||
.pnp.* |
||||
# mac files |
||||
.DS_Store |
||||
|
||||
# ---> Vue |
||||
# gitignore template for Vue.js projects |
||||
# |
||||
# Recommended template: Node.gitignore |
||||
# vim swap files |
||||
*.swp |
||||
|
||||
# TODO: where does this rule come from? |
||||
docs/_book |
||||
# webstorm |
||||
.idea |
||||
|
||||
# TODO: where does this rule come from? |
||||
test/ |
||||
# vscode |
||||
.vscode |
||||
*code-workspace |
||||
|
||||
# clinic |
||||
profile* |
||||
*clinic* |
||||
*flamegraph* |
||||
|
@ -0,0 +1,135 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: hutao
|
||||
// | @文件描述: app.js -
|
||||
// | @创建时间: 2024-03-15 11:38
|
||||
// | @更新时间: 2024-03-15 11:38
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
import config from "config"; |
||||
import Fastify from "fastify"; |
||||
import {isLowerCase, isTrim} from "#plugins/ajv/index.js"; |
||||
import ajvErrors from "ajv-errors"; |
||||
import chalk from "chalk"; |
||||
import {destr} from 'destr' |
||||
|
||||
global.tostr = JSON.stringify |
||||
global.destr = destr |
||||
|
||||
const fastify = Fastify({ |
||||
ajv: { |
||||
customOptions: { |
||||
removeAdditional: true, |
||||
useDefaults: true, |
||||
coerceTypes: true, |
||||
allErrors: true, |
||||
// 允许使用联合模式 严格模式下
|
||||
allowUnionTypes: true |
||||
}, |
||||
plugins: [ |
||||
ajvErrors, |
||||
// 这种方式完美解决,开心, 实际上这是一个函数,会传进去ajv实例对象
|
||||
isLowerCase, |
||||
isTrim, |
||||
] |
||||
|
||||
} |
||||
}) |
||||
fastify.decorate('color', chalk) |
||||
fastify.decorate('config', config) |
||||
fastify.decorate('authenticate', async function (request, reply) { |
||||
try { |
||||
// 校验token,并生成request的user参数
|
||||
await request.jwtVerify(); |
||||
} catch (err) { |
||||
// token校验未通过,记录错误
|
||||
fastify.log.debug({reqId: request.id, Auth: err.message}); |
||||
reply.code(err.statusCode).send({ |
||||
statusCode: err.statusCode, |
||||
message: err.message, |
||||
error: err.name |
||||
}); |
||||
} |
||||
}); |
||||
// | 插件
|
||||
// @ 注册日志记录
|
||||
await fastify.register(import("#common/logger/index.js")) |
||||
// @ 注册数据库
|
||||
await fastify.register(import("#plugins/sequelize/index.js")) |
||||
// @ 注册redis
|
||||
await fastify.register(import('@fastify/redis'), { |
||||
// 配置文件基础redis配置 host,port,password
|
||||
host: '172.16.1.10', |
||||
port: 6379, |
||||
password: 'Hxl1314521', |
||||
// fastify调用的名称空间
|
||||
namespace: 'db1', |
||||
// 指向的redis数据库0-15
|
||||
db: 1, |
||||
// 4 (IPv4) or 6 (IPv6)
|
||||
family: 4, |
||||
// redis连接中的名字
|
||||
connectionName: 'global.conf.projectName' |
||||
}); |
||||
// @ 注册防止恶意请求封ip
|
||||
await fastify.register(import('@fastify/rate-limit'), { |
||||
// 限制每个 IP 地址在时间窗口内最多可以发出的请求数
|
||||
max: 500, |
||||
// '1 second' = '1000':1 秒 | '1 minute' 1 分钟 | '1 hour' 1 小时 | '1 day' 1 天 | '1 week' 1 周
|
||||
timeWindow: '1 day' |
||||
}); |
||||
// @ 注册错误处理工具
|
||||
await fastify.register(import('@fastify/sensible')); |
||||
// @ 注册jwt
|
||||
await fastify.register(import('@fastify/jwt'), { |
||||
// token的加密值
|
||||
secret: 'xadvdgfhga21xabhgnumo;opilkujya8axa21', |
||||
sign: { |
||||
// token过期时间
|
||||
expiresIn: '20m' |
||||
} |
||||
}); |
||||
// @ 注册路由
|
||||
await fastify.register(import('#routes/index.js'), {prefix: '/api'}) |
||||
|
||||
// @ 发送消息拦截
|
||||
fastify.addHook('onSend', async (request, reply, payload) => { |
||||
// fastify.log.info(reply.getHeader('content-type'));
|
||||
if (reply.statusCode === 200 && !request.url.includes('/api/file')) { |
||||
if(['{', '"'].indexOf(payload.trim().slice(0, 1)[0]) > -1){ |
||||
return `{"statusCode": 200, "status": "success", "data": ${payload}}`; |
||||
}else{ |
||||
reply.header('Content-Type', 'application/json'); |
||||
return `{"statusCode": 200, "status": "success", "data": "${payload}"}`; |
||||
} |
||||
} |
||||
return payload; |
||||
}); |
||||
// @ 错误拦截
|
||||
fastify.setErrorHandler(async (error, request, reply) => { |
||||
// 自定义错误处理逻辑
|
||||
fastify.log.error(error); |
||||
reply |
||||
.code(error.statusCode) |
||||
.send({ |
||||
statusCode: error.statusCode, |
||||
message: error.message, |
||||
error: error.name |
||||
}); |
||||
}); |
||||
|
||||
fastify.logger.info(await fastify.redis.db1.set('name', 'xsx', 'EX', 60)); |
||||
|
||||
// console.log(fastify.data)
|
||||
|
||||
|
||||
fastify.listen({ |
||||
port: config.get('port') |
||||
}).then(resd => { |
||||
console.log(`http://127.0.0.1:${config.get('port')}`) |
||||
}) |
@ -0,0 +1,13 @@ |
||||
{ |
||||
"ProjectName": "HuTao", |
||||
"timestamp": "20240315", |
||||
"host": "0.0.0.0", |
||||
"port": "48826", |
||||
"db": { |
||||
"username": "nie", |
||||
"password": "Hxl1314521", |
||||
"database": "hutao", |
||||
"host": "172.16.1.10", |
||||
"port": "3306" |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
version: '3.8' |
||||
|
||||
services: |
||||
mysql: |
||||
image: mysql:8 |
||||
restart: always |
||||
environment: |
||||
MYSQL_ROOT_PASSWORD: Hxl1314521 |
||||
MYSQL_DATABASE: hutao |
||||
MYSQL_USER: nie |
||||
MYSQL_PASSWORD: Hxl1314521 |
||||
ports: |
||||
- "3306:3306" |
||||
volumes: |
||||
- mysql8:/var/lib/mysql |
||||
|
||||
redis: |
||||
image: redis:latest |
||||
restart: always |
||||
ports: |
||||
- "6379:6379" |
||||
volumes: |
||||
- ./redis.conf:/usr/local/etc/redis/redis.conf |
||||
- redis_data:/data |
||||
- redis_log:/var/log/redis |
||||
|
||||
redisinsight: |
||||
image: redislabs/redisinsight:latest |
||||
ports: |
||||
- "8001:8001" |
||||
|
||||
volumes: |
||||
mysql8: |
||||
redis_data: |
||||
redis_log: |
@ -0,0 +1,213 @@ |
||||
Fastify 提供了多种钩子(hooks),允许您在不同的生命周期阶段介入服务器的行为。它们在处理请求和响应时非常有用,例如验证请求、日志记录、执行清理或统计任务等。 |
||||
|
||||
下面是 Fastify 所有的生命周期钩子列表,按照它们在请求生命周期中的执行顺序排列: |
||||
|
||||
### 应用层面的钩子 |
||||
|
||||
1. `onRequest(req, reply, done)` |
||||
- 在路由解析之前执行(但在请求被解析之后,例如 HTTP 方法、URL 和头部)。 |
||||
- 可以用来执行验证、解密等任务。 |
||||
|
||||
2. `preParsing(req, reply, payload, done)` |
||||
- 在解析 HTTP 请求之前执行(例如,解析请求体之前)。 |
||||
- 允许您操作原始请求对象和有效载荷。 |
||||
|
||||
3. `preValidation(req, reply, done)` |
||||
- 在执行路由处理程序之前、请求校验之前执行。 |
||||
- 适合执行权限校验等任务。 |
||||
|
||||
4. `preHandler(req, reply, done)` |
||||
- 在执行路由处理程序之前执行。 |
||||
- 可用于修改请求或回复,或在实际处理程序运行前做准备工作。 |
||||
|
||||
### 路由层面的钩子 |
||||
|
||||
在路由声明时,可以指定以下钩子,它们仅在应用于该路由的请求中执行: |
||||
|
||||
1. `preValidation(req, reply, done)` |
||||
- 类似于应用层面的 preValidation 钩子,但只作用于特定路由。 |
||||
|
||||
2. `preHandler(req, reply, done)` |
||||
- 类似于应用层面的 preHandler 钩子,但只作用于特定路由。 |
||||
|
||||
### 异常处理钩子 |
||||
|
||||
1. `onError(req, reply, error, done)` |
||||
- 当处理程序或钩子抛出异常时执行。 |
||||
- 用于自定义错误处理逻辑。 |
||||
|
||||
### 回复生命周期钩子 |
||||
|
||||
1. `onSend(req, reply, payload, done)` |
||||
- 在将回复发送给客户端之前执行。 |
||||
- 可以用来修改回复的有效载荷。 |
||||
|
||||
2. `onResponse(req, reply, done)` |
||||
- 当响应完全发送给客户端后执行。 |
||||
- 适用于清理资源或记录日志。 |
||||
|
||||
### 关闭钩子 |
||||
|
||||
1. `onClose(instance, done)` |
||||
- 在服务器关闭时执行。 |
||||
- 通常用于关闭数据库连接、清理资源等。 |
||||
|
||||
这些钩子都遵循 `fastify` 的 "错误优先回调" 约定,即 `done` 回调函数的第一个参数是一个错误对象,如果有错误的话。如果一切正常,您只需调用 `done()`,没有任何参数或者传入 `null`。 |
||||
|
||||
要使用这些钩子,通常您会将它们注册为 Fastify 插件,或者直接应用在 Fastify 实例上。例如: |
||||
|
||||
```javascript |
||||
// 注册一个 onRequest 钩子 |
||||
fastify.addHook('onRequest', (req, reply, done) => { |
||||
// 钩子逻辑 |
||||
done(); |
||||
}); |
||||
``` |
||||
|
||||
对于路由特定的钩子,您将它们添加到路由声明中,如下所示: |
||||
|
||||
```javascript |
||||
fastify.route({ |
||||
method: 'GET', |
||||
url: '/', |
||||
preHandler: (req, reply, done) => { |
||||
// 路由特定的钩子逻辑 |
||||
done(); |
||||
}, |
||||
handler: (req, reply) => { |
||||
// 路由处理程序 |
||||
reply.send({ hello: 'world' }); |
||||
} |
||||
}); |
||||
``` |
||||
|
||||
钩子可以是非常强大的工具,它们提供了对 Fastify 应用程序的细粒度控制,允许您优雅地集成自定义逻辑和中间件。 |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
在 Fastify 中,您可以通过在路由声明中直接添加钩子来将它们作用于特定的路由。当您声明路由时,Fastify 允许您指定 `preValidation`、`preHandler`、`preSerialization`、`onError`、`onSend` 和 `onResponse` 这些生命周期钩子。 |
||||
|
||||
这里有一个例子,展示了如何给特定路由添加钩子: |
||||
|
||||
```javascript |
||||
fastify.route({ |
||||
method: 'GET', |
||||
url: '/special', |
||||
preHandler: (request, reply, done) => { |
||||
// ... 在这个特定路由之前执行一些逻辑 |
||||
done(); |
||||
}, |
||||
handler: (request, reply) => { |
||||
// ... 处理路由请求 |
||||
reply.send({ hello: 'world' }); |
||||
}, |
||||
onSend: (request, reply, payload, done) => { |
||||
// ... 在回复发送到客户端之前修改回复内容 |
||||
done(); |
||||
} |
||||
}); |
||||
``` |
||||
|
||||
在这个例子中,`preHandler` 钩子函数在处理 GET 请求到 `/special` 路由之前运行,而 `onSend` 钩子在响应发送之前运行,可以用于修改响应 payload。 |
||||
|
||||
每个钩子函数都会接受不同的参数,根据所执行的任务,这些参数提供了对请求 (`request`)、回复 (`reply`)、有效载载荷 (`payload`) 等的访问权。钩子函数最后需要调用 `done` 函数来通知 Fastify 钩子已完成执行,如果出现错误,可以将错误作为 `done` 函数的第一个参数传递。如果一切顺利,那么只需调用 `done()` 或 `done(null)` 即可。 |
||||
|
||||
如果您需要在一个特定的路由上应用多个同类型的钩子,您可以传递一个钩子函数的数组: |
||||
|
||||
```javascript |
||||
fastify.route({ |
||||
method: 'GET', |
||||
url: '/special', |
||||
preHandler: [ |
||||
(request, reply, done) => { |
||||
// 第一个 preHandler 钩子 |
||||
done(); |
||||
}, |
||||
(request, reply, done) => { |
||||
// 第二个 preHandler 钩子 |
||||
done(); |
||||
} |
||||
], |
||||
handler: (request, reply) => { |
||||
// ...处理路由请求 |
||||
reply.send({ hello: 'world' }); |
||||
} |
||||
}); |
||||
``` |
||||
|
||||
在这个例子中,当请求 `/special` 路由时,将按照数组中的顺序执行两个 `preHandler` 钩子函数。 |
||||
|
||||
通过将钩子绑定到特定路由上,您可以为每个路由精细地控制流程,并按需集成权限检查、请求数据修改、响应的自定义处理等逻辑。 |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
在 Fastify 中,如果多个路由有部分共同的路径,并且你想对这些具有共同路径的路由应用特定的钩子,你可以使用两种主要方法: |
||||
|
||||
1. **通过插件和前缀注册钩子**:你可以创建一个插件,为这个插件设置一个路径前缀(路由的共同部分),然后在这个插件内注册全局钩子(这将仅应用于插件范围内的路由)。 |
||||
|
||||
以下是使用插件来注册一个钩子的示例: |
||||
|
||||
```javascript |
||||
const fastify = require('fastify')({ logger: true }); |
||||
|
||||
// 插件定义 |
||||
fastify.register(function (instance, options, done) { |
||||
// 这里的钩子将仅作用于 '/api' 前缀路径下的路由 |
||||
instance.addHook('preHandler', async (request, reply) => { |
||||
// ... 在该插件下所有路由的处理器之前执行逻辑 |
||||
}); |
||||
|
||||
instance.get('/feature1', async (request, reply) => { |
||||
// ... 处理该路由的请求 |
||||
return { feature: '1' }; |
||||
}); |
||||
|
||||
instance.get('/feature2', async (request, reply) => { |
||||
// ... 处理该路由的请求 |
||||
return { feature: '2' }; |
||||
}); |
||||
|
||||
done(); |
||||
}, { prefix: '/api' }); |
||||
|
||||
// 启动服务器 |
||||
fastify.listen(3000, err => { |
||||
if (err) throw err; |
||||
console.log(`Server listening on ${fastify.server.address().port}`); |
||||
}); |
||||
``` |
||||
|
||||
在这个例子中,所有前缀为 `/api` 的路由都将执行在 `addHook` 中定义的 `preHandler` 钩子。 |
||||
|
||||
2. **条件性地在钩子中检查路由路径**:你还可以在一个全局钩子中检查请求的路径,根据请求的路由路径来做出条件性的反应,这种方法没有上面那种结构化,但在某些情况下可能有用。 |
||||
|
||||
以下是使用条件性钩子的示例: |
||||
|
||||
```javascript |
||||
// 全局钩子 |
||||
fastify.addHook('preHandler', async (request, reply) => { |
||||
// 查看请求路径是否匹配特定模式 |
||||
if (request.raw.url.startsWith('/api')) { |
||||
// ... 在所有 '/api' 路径下的路由之前执行逻辑 |
||||
} |
||||
}); |
||||
|
||||
// 非`/api`前缀的路由 |
||||
fastify.get('/other', async (request, reply) => { |
||||
// 这个路由不会受到上述全局钩子影响 |
||||
// 除非在上面的条件检查中同时包括此路径 |
||||
return { hello: 'world' }; |
||||
}); |
||||
``` |
||||
|
||||
在这个例子中,全局 `preHandler` 钩子对所有路由生效,但是它内部检查请求的 URL 是不是以 `/api` 开头,仅对符合该条件的路径执行额外的逻辑。 |
||||
|
||||
通常,对于维护性和可读性而言,使用插件和前缀注册钩子的方法更为推荐,因为它将相关的路由和钩子逻辑组织在一起,并避免了全局状态的潜在冲突。 |
@ -0,0 +1,51 @@ |
||||
在 Fastify 中, `register` 函数用于注册插件。插件可以是一组封装的路由、服务、插件或者是装饰器等。使用 `register` 可以为应用添加新的功能或改变其默认行为。`register` 函数接受两个参数:插件本身及一个可选的选项对象。 |
||||
|
||||
### 1. 插件 (Plugin) |
||||
|
||||
第一个参数是插件本身,它可以是一个异步函数,该函数接受三个参数: |
||||
- `fastify` 实例:当前服务器实例的引用。 |
||||
- `options`:传递给插件的选项对象。 |
||||
- `done`:一个回调函数,用于声明插件的注册完成。 |
||||
|
||||
### 2. 选项 (Options) |
||||
|
||||
第二个参数是一个可选的选项对象,用于提供给插件额外的配置。 |
||||
|
||||
一个 `register` 调用的基本结构如下: |
||||
|
||||
```javascript |
||||
fastify.register( |
||||
plugin, // 插件函数 |
||||
options // 可选的配置对象 |
||||
); |
||||
``` |
||||
|
||||
`options` 对象可以含有以下属性: |
||||
|
||||
- `prefix`:一个字符串值,为插件内所有路由添加前缀,很有用当你需要版本化 API 或者有多个相似的路由集合。 |
||||
- 其他自定义属性:这些属性将传递给插件函数内的 `options` 参数,可以根据插件的需求来设定。不同的插件可能需要不同的选项。 |
||||
|
||||
### 示例: |
||||
|
||||
```javascript |
||||
fastify.register( |
||||
require('fastify-plugin'), |
||||
{ prefix: '/api/v1' } // 为这个插件下的所有路由设置前缀 |
||||
); |
||||
|
||||
// 或者使用 async/await 语法 |
||||
fastify.register(async (instance, opts) => { |
||||
instance.get('/route', async (request, reply) => { |
||||
return { hello: 'world' }; |
||||
}); |
||||
}, { prefix: '/api/v1' }); |
||||
``` |
||||
|
||||
在第二个例子中,我们定义了一个匿名插件函数并将其注册到 Fastify 实例中,并通过 `options` 对象设置了一个 `prefix`。这意味着,插件内部定义的所有路由都将自动添加 `/api/v1` 作为路径前缀。 |
||||
|
||||
### 注意: |
||||
|
||||
- `fastify-plugin`:这是一个可以保证插件注册的声明周期钩子和装饰器在父作用域中也同样可见的工具,通常用于那些需要全局可访问的插件。 |
||||
- 插件隔离:Fastify 实例是通过原型链继承创建的,这表示每个插件拥有自己的封装作用域。插件内的装饰器、钩子等,不会影响到其他插件或主应用,除非使用了 `fastify-plugin`。 |
||||
|
||||
参考 [Fastify 插件文档](https://www.fastify.io/docs/latest/Reference/Plugins/) 可以提供更详细的信息和高级配置选项。 |
@ -0,0 +1,238 @@ |
||||
在Fastify中,可以通过路由拆分来组织和维护大型应用的代码。路由拆分通常意味着你将相关的路由处理函数放到单独的文件或模块中,并在应用中相应地注册它们。下面是如何做到路由拆分的一般步骤: |
||||
|
||||
1. 创建一个路由文件或模块。 |
||||
2. 在该文件中定义路由。 |
||||
3. 导出这些路由。 |
||||
4. 在你的主应用文件中注册这些路由。 |
||||
|
||||
### 例子:创建和使用路由模块 |
||||
|
||||
以下给出一个非常基础的例子来演示如何拆分路由。 |
||||
|
||||
#### user-routes.js |
||||
```javascript |
||||
// user-routes.js |
||||
async function routes(fastify, options) { |
||||
fastify.get('/users', async (request, reply) => { |
||||
// 处理获取用户的逻辑... |
||||
return { users: [] }; |
||||
}); |
||||
|
||||
fastify.get('/users/:id', async (request, reply) => { |
||||
// 处理获取单个用户的逻辑... |
||||
const { id } = request.params; |
||||
return { user: id }; |
||||
}); |
||||
} |
||||
|
||||
module.exports = routes; |
||||
``` |
||||
|
||||
#### item-routes.js |
||||
```javascript |
||||
// item-routes.js |
||||
async function routes(fastify, options) { |
||||
fastify.get('/items', async (request, reply) => { |
||||
// 处理获取物品的逻辑... |
||||
return { items: [] }; |
||||
}); |
||||
|
||||
fastify.get('/items/:id', async (request, reply) => { |
||||
// 处理获取单个物品的逻辑... |
||||
const { id } = request.params; |
||||
return { item: id }; |
||||
}); |
||||
} |
||||
|
||||
module.exports = routes; |
||||
``` |
||||
|
||||
#### 主应用文件(比如app.js或server.js) |
||||
```javascript |
||||
// app.js |
||||
const fastify = require('fastify')({ logger: true }); |
||||
|
||||
// 注册路由模块 |
||||
fastify.register(require('./user-routes')); |
||||
fastify.register(require('./item-routes')); |
||||
|
||||
// 启动服务器 |
||||
fastify.listen({ port: 3000, host: '0.0.0.0' }, (err, address) => { |
||||
if (err) { |
||||
fastify.log.error(err); |
||||
process.exit(1); |
||||
} |
||||
console.log(`Server listening on ${address}`); |
||||
}); |
||||
``` |
||||
|
||||
在这个例子中,我们有两个路由模块(`user-routes.js` 和 `item-routes.js`),它们被导入到主应用文件 `app.js` 中,使用 `fastify.register` 方法来注册。每个路由文件处理特定路径的请求,并有自己的路由处理函数。这种方式使得代码结构更清晰,并且容易维护,特别是当应用程序规模变大时。 |
||||
|
||||
为了更好的代码组织,你甚至可以进一步把路由处理函数分离到独立的控制器文件中。但以上示例提供了一个如何开始拆分路由的基础结构。 |
||||
|
||||
|
||||
|
||||
|
||||
*** |
||||
|
||||
|
||||
如果你想进一步组织你的代码,尤其是对于大型应用程序,以下是一些可以考虑的额外步骤: |
||||
|
||||
### 1. 分离控制器 |
||||
|
||||
可以创建一个控制器目录,用于存放处理路由逻辑的函数。这能让你的路由定义更加简洁,因为控制器将包含大部分业务逻辑。 |
||||
|
||||
**controllers/userController.js** |
||||
```javascript |
||||
async function getUsers(request, reply) { |
||||
// 处理逻辑... |
||||
return { users: [] }; |
||||
} |
||||
|
||||
async function getUserById(request, reply) { |
||||
const { id } = request.params; |
||||
// 处理逻辑... |
||||
return { user: id }; |
||||
} |
||||
|
||||
module.exports = { |
||||
getUsers, |
||||
getUserById, |
||||
}; |
||||
``` |
||||
|
||||
然后,在路由文件中,你可以导入并使用这些控制器函数: |
||||
|
||||
**routes/user-routes.js** |
||||
```javascript |
||||
const userController = require('../controllers/userController'); |
||||
|
||||
async function routes(fastify, options) { |
||||
fastify.get('/users', userController.getUsers); |
||||
fastify.get('/users/:id', userController.getUserById); |
||||
} |
||||
|
||||
module.exports = routes; |
||||
``` |
||||
|
||||
### 2. 路由前缀 |
||||
|
||||
当注册路由时,可以为一组路由指定一个公共前缀。Fastify 允许你在注册插件时提供一个 `prefix` 选项,该选项会应用于该插件中定义的所有路由。 |
||||
|
||||
```javascript |
||||
fastify.register(require('./user-routes'), { prefix: '/users' }); |
||||
fastify.register(require('./item-routes'), { prefix: '/items' }); |
||||
``` |
||||
|
||||
这将相应地更改 `user-routes.js` 和 `item-routes.js` 中的路径定义,因为你不再需要在每个路由路径中包含 `/users` 或 `/items`。 |
||||
|
||||
### 3. 插件化 |
||||
|
||||
为了进一步的模块化,可以将某些功能片段封装为插件。Fastify 天然支持插件架构,以便于封装和共享代码。例如,可以创建一个为特定资源服务的插件,将路由处理和相关功能封装在内。 |
||||
|
||||
**plugins/users.js** |
||||
```javascript |
||||
const userRoutes = require('../routes/user-routes'); |
||||
|
||||
async function usersPlugin(fastify, options) { |
||||
fastify.decorate('someUtility', () => {/* ... */}); |
||||
fastify.register(userRoutes); |
||||
} |
||||
|
||||
module.exports = usersPlugin; |
||||
``` |
||||
|
||||
注册插件: |
||||
|
||||
**app.js** |
||||
```javascript |
||||
fastify.register(require('./plugins/users'), { prefix: '/users' }); |
||||
``` |
||||
|
||||
### 4. 结构化错误处理和验证 |
||||
|
||||
Fastify 提供了丰富的钩子(hooks)和插件来处理错误和验证请求。你可以创建共享的钩子和模式,在你的应用中使用它们,以保持代码的一致性和减少重复。 |
||||
|
||||
### 5. 使用 `fastify-cli` 工具 |
||||
|
||||
Fastify 社区还提供了 `fastify-cli` 命令行工具,用于快速地搭建和管理 Fastify 项目。它可以自动为你生成项目结构,包括路由、插件和其他配置,从而让你更容易地按照最佳实践组织代码。 |
||||
|
||||
通过使用上述步骤,你将能够创建一个更加模块化、易于维护和可扩展的 Fastify 应用程序。 |
||||
|
||||
|
||||
*** |
||||
|
||||
|
||||
在 Fastify 中,可以使用钩子(Hooks)来执行拦截和验证操作。钩子可以在请求的生命周期的不同阶段被触发,例如在请求被路由之前(`onRequest`)、在执行路由处理函数之前(`preValidation`、`preHandler`)、在发送响应之前(`onSend`)等等。 |
||||
|
||||
若要针对不同的路由模块执行不同的拦截验证,你可以在每个路由模块中分别声明这些钩子。这样,只有当请求达到特定模块的路由时,相应的钩子才会被触发。以下是如何为不同的路由模块添加特定逻辑的示例: |
||||
|
||||
### 创建一个带有 onRequest 钩子的路由模块 |
||||
|
||||
```javascript |
||||
// routes/user-routes.js |
||||
module.exports = async function (fastify, options) { |
||||
// 在 '/users' 路径及其子路径上应用 onRequest 钩子 |
||||
fastify.addHook('onRequest', async (request, reply) => { |
||||
// 添加一些用户身份验证逻辑 |
||||
if (!request.headers['auth-token']) { |
||||
throw new Error('Missing auth token!'); |
||||
} |
||||
// 检查 auth-token 是否有效... |
||||
}); |
||||
|
||||
// 用户路由 |
||||
fastify.get('/users', async (request, reply) => { |
||||
// 返回用户列表 |
||||
}); |
||||
|
||||
fastify.get('/users/:id', async (request, reply) => { |
||||
// 返回单个用户 |
||||
}); |
||||
}; |
||||
``` |
||||
|
||||
### 创建另一个带有 preHandler 钩子的路由模块 |
||||
|
||||
```javascript |
||||
// routes/item-routes.js |
||||
module.exports = async function (fastify, options) { |
||||
// 在 '/items' 路径及其子路径上应用 preHandler 钩子 |
||||
fastify.addHook('preHandler', async (request, reply) => { |
||||
// 添加一些权限检查逻辑 |
||||
const userRole = request.user.role; // 假设我们已经在earlier hook中设置了用户 |
||||
if (userRole !== 'admin') { |
||||
throw new Error('Unauthorized!'); |
||||
} |
||||
// 执行其它必要的权限检查... |
||||
}); |
||||
|
||||
// 物品路由 |
||||
fastify.get('/items', async (request, reply) => { |
||||
// 返回物品列表 |
||||
}); |
||||
|
||||
fastify.get('/items/:id', async (request, reply) => { |
||||
// 返回单个物品详情 |
||||
}); |
||||
}; |
||||
``` |
||||
|
||||
在主应用文件中,你需要注册上述路由模块: |
||||
|
||||
```javascript |
||||
// app.js |
||||
const fastify = require('fastify')(); |
||||
|
||||
// 注册用户路由模块,'onRequest' 钩子将适用于此路由及其子路由 |
||||
fastify.register(require('./routes/user-routes'), { prefix: '/users' }); |
||||
|
||||
// 注册物品路由模块,'preHandler' 钩子将适用于此路由及其子路由 |
||||
fastify.register(require('./routes/item-routes'), { prefix: '/items' }); |
||||
|
||||
// 运行服务器... |
||||
``` |
||||
|
||||
在这个例子中,用户相关的路由会检查请求头中是否包含 `auth-token`,而物品相关的路由则会在路由处理之前检查用户的角色。这种方式能够让你按模块进行细粒度的拦截和验证控制,同时保持代码的整洁和模块化。 |
||||
|
||||
值得注意的是,钩子内部抛出的错误将被 Fastify 捕获,并作为 HTTP 错误被发送给客户端。这样,你可以根据需要定制错误响应。此外,Fastify 还允许你通过 `fastify.decorateRequest` 和 `fastify.decorateReply` 方法给 `request` 和 `reply` 对象添加自定义属性和方法,进一步增强拦截和验证的能力。 |
@ -0,0 +1,181 @@ |
||||
## 安装Docker |
||||
|
||||
```bash |
||||
sudo apt-get install docker docker-compose |
||||
|
||||
#查看Docker可用用户 |
||||
sudo cat /etc/group |grep docker |
||||
# docker:x:998:表示存在 |
||||
sudo groupadd docker |
||||
sudo usermod -aG docker $USER |
||||
newgrp docker # 激活组更改,无效的话尝试重连ssh,退出登录,重启计算机等 |
||||
|
||||
# 查看版本 |
||||
docker version |
||||
``` |
||||
## docker-compose操作 |
||||
|
||||
```bash |
||||
# 启动当前目录下的docker-compose的yaml文件 |
||||
docker-compose up |
||||
# 后台运行 |
||||
docker-compose up -d |
||||
# 启动文件中的指定服务 |
||||
docker-compose up -d 服务名称 |
||||
|
||||
# 停止所有服务 这个命令会停止并移除所有容器,网络,但不包括数据卷 |
||||
docker-compose down |
||||
# 仅停止服务不移除容器 |
||||
docker-compose stop |
||||
# 停止特定服务 |
||||
docker-compose stop 服务名称 |
||||
# 重新启动服务 |
||||
docker-compose restart |
||||
``` |
||||
|
||||
## docker卷操作 |
||||
|
||||
```bash |
||||
# 创建卷 |
||||
# docker volume create [OPTIONS] [VOLUME] |
||||
# 这个命令用于创建一个新的卷。你可以指定卷的名称,如果不指定,Docker 会为你生成一个随机名称。此外,还可以通过选项设置卷的各种属性。 |
||||
docker volume create my_volume |
||||
|
||||
|
||||
# 列出所有现有的卷 |
||||
docker volume ls |
||||
|
||||
# 检查卷 |
||||
# docker volume inspect [VOLUME_NAME] |
||||
# 显示某个卷的详细信息,包括其名称、驱动、挂载点等 |
||||
docker volume inspect my_volume |
||||
|
||||
# 删除卷 |
||||
# docker volume rm [VOLUME_NAME] |
||||
# 此命令用于删除指定的卷。如果某个卷当前被一个或多个容器使用,那么这个卷不能被删除,除非使用 -f 或 --force 选项 |
||||
docker volume rm my_volume |
||||
|
||||
# 清理未使用的卷 |
||||
# 此命令会删除所有未被任何容器使用的卷。执行此命令前,Docker 通常会请求确认 |
||||
docker volume prune |
||||
``` |
||||
|
||||
## 文件示例 |
||||
```yaml |
||||
version: '3.8' |
||||
|
||||
services: |
||||
mysql: |
||||
image: mysql:8 |
||||
restart: always |
||||
environment: |
||||
MYSQL_ROOT_PASSWORD: Hxl1314521 |
||||
MYSQL_DATABASE: hutao |
||||
MYSQL_USER: nie |
||||
MYSQL_PASSWORD: Hxl1314521 |
||||
ports: |
||||
- "3306:3306" |
||||
volumes: |
||||
- mysql8:/var/lib/mysql |
||||
|
||||
redis: |
||||
image: redis:latest |
||||
restart: always |
||||
ports: |
||||
- "6379:6379" |
||||
volumes: |
||||
- ./redis.conf:/usr/local/etc/redis/redis.conf |
||||
- redis_data:/data |
||||
- redis_log:/var/log/redis |
||||
|
||||
redisinsight: |
||||
image: redislabs/redisinsight:latest |
||||
ports: |
||||
- "8001:8001" |
||||
|
||||
volumes: |
||||
mysql8: |
||||
redis_data: |
||||
redis_log: |
||||
``` |
||||
|
||||
|
||||
## redis配置 |
||||
```editorconfig |
||||
# Redis持久化选项 |
||||
# 使用RDB持久化 |
||||
# |
||||
# save 900 1 |
||||
# save 300 10 |
||||
# save 60 10000 |
||||
|
||||
# AOF持久化开关 |
||||
#appendonly yes |
||||
# AOF文件写入方式 |
||||
#appendfsync everysec |
||||
# AOF重写时,是否减少写操作的阻塞 |
||||
#no-appendfsync-on-rewrite no |
||||
|
||||
# Redis安全设置 |
||||
# 设置密码(这里设置的密码为yourpassword,请替换为你自己的密码) |
||||
requirepass Hxl1314521 |
||||
|
||||
# 网络设置 |
||||
# 绑定的IP,如果需要远程连接,请注释掉这行 |
||||
# bind 127.0.0.1 |
||||
# 监听的端口,默认为6379 |
||||
#port 6379 |
||||
|
||||
# 日志文件位置 |
||||
logfile "/var/log/redis/redis-server.log" |
||||
|
||||
# 最大内存使用量,超出则根据淘汰策略删除键,示例为无限制 |
||||
# maxmemory <bytes> |
||||
# 内存淘汰策略 |
||||
# maxmemory-policy noeviction |
||||
``` |
||||
|
||||
|
||||
## Docker 安装 |
||||
|
||||
```bash |
||||
# 更新软件包索引 |
||||
sudo apt-get update |
||||
# 安装所需的包 |
||||
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common |
||||
# 添加Docker的官方GPG密钥 |
||||
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg |
||||
# 设置稳定版仓库 |
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null |
||||
# 再次更新软件包索引(此时包括Docker仓库) |
||||
sudo apt-get update |
||||
# 安装Docker Engine |
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io |
||||
# 验证Docker是否安装成功 |
||||
sudo docker version |
||||
# Docker作为非root用户 |
||||
sudo usermod -aG docker $USER |
||||
newgrp docker |
||||
# 开启远程访问 |
||||
sudo systemctl edit docker.service |
||||
#[Service] |
||||
#ExecStart= |
||||
#ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375 |
||||
sudo systemctl daemon-reload |
||||
sudo systemctl restart docker.service |
||||
sudo netstat -lntp | grep dockerd |
||||
docker -H tcp://127.0.0.1:2375 version |
||||
``` |
||||
|
||||
## 卸载docker |
||||
|
||||
```bash |
||||
# 删除软件包及其配置文件 |
||||
sudo apt-get purge docker-ce docker-ce-cli containerd.io docker-compose |
||||
# 删除资源 |
||||
sudo rm -rf /var/lib/docker |
||||
sudo rm -rf /var/lib/containerd |
||||
# 删除Docker的GPG密钥和源列表文件 |
||||
sudo rm /usr/share/keyrings/docker-archive-keyring.gpg |
||||
sudo rm /etc/apt/sources.list.d/docker.list |
||||
``` |
@ -0,0 +1,185 @@ |
||||
# 数据库查询 |
||||
|
||||
## 一、基础查询 |
||||
|
||||
### 1.1SELECT |
||||
|
||||
#### 查全部 |
||||
|
||||
```sql |
||||
SELECT * FROM table; |
||||
``` |
||||
|
||||
#### 查指定字段 |
||||
|
||||
```sql |
||||
SELECT name,age FROM table; |
||||
``` |
||||
|
||||
#### 字段别名 |
||||
|
||||
```sql |
||||
SELECT name as newName,age as '年龄' FROM table; |
||||
``` |
||||
|
||||
#### 过滤字段重复 |
||||
|
||||
```sql |
||||
# 过滤掉目标所有字段都重复的行 |
||||
SELECT DISTINCT name, age FROM table; |
||||
``` |
||||
### 1.2聚合函数 |
||||
|
||||
```sql |
||||
SELECT COUNT(id) AS '数量', SUM(id) AS '总和', AVG(id) AS '平均数', MAX(id) AS '最大数', MIN(id) AS '最小数' FROM table; |
||||
``` |
||||
|
||||
### 1.3WHERE |
||||
|
||||
```sql |
||||
SELECT * FROM table WHERE id = 1; |
||||
/* SELECT * FROM user_info_structs WHERE user_info_struct_sequence= '3' OR field_name = '头像'; */ |
||||
/* SELECT * FROM user_info_structs WHERE user_info_struct_sequence= '1' AND field_name = '头像'; */ |
||||
``` |
||||
|
||||
### 1.4模糊查询 LIKE |
||||
|
||||
```sql |
||||
/* |
||||
任意字符 % |
||||
单一字符 _ |
||||
*/ |
||||
SELECT name WHERE name LIKE '何%'; |
||||
/* |
||||
何% 何(任意个字符) |
||||
何_ 何X |
||||
何__ 何XX |
||||
*/ |
||||
``` |
||||
|
||||
### 1.5排序 ORDER BY |
||||
|
||||
```sql |
||||
/* |
||||
默认 ASC 从小到大 升序 |
||||
DESC 从大到小 降序 |
||||
*/ |
||||
SELECT * FROM table ORDER BY id DESC; |
||||
SELECT * FROM user_info_structs WHERE field_name LIKE '手机%' ORDER BY user_info_struct_sequence ASC; |
||||
/* ORDER BY 要写在 WHERE后面*/ |
||||
|
||||
/*多顺序*/ |
||||
SELECT * FROM table ORDER BY id DESC, name ASC; |
||||
``` |
||||
|
||||
### 1.6分组 GROUP BY |
||||
|
||||
```sql |
||||
/* |
||||
分组只会出现分组数量的列 |
||||
最好用分组目标作为一个列明区分,然后用count之类的几何函数 |
||||
ORDER BY放在WHERE后面 |
||||
HAVING 相当于条件中的条件 放在ORDER BY后面 |
||||
|
||||
*/ |
||||
SELECT field_name,COUNT(user_info_struct_sequence) AS 'num' FROM user_info_structs WHERE user_info_struct_sequence >= 1 GROUP BY field_name HAVING field_name != '手机'; |
||||
``` |
||||
### 1.7分页LIMIT |
||||
|
||||
```sql |
||||
/*LIMIT要放在后面,在ORDER BY之后*/ |
||||
|
||||
SELECT * FROM table WHERE id >= 3 ORDER BY age DESC LIMIT 0,10; |
||||
SELECT * FROM user_info_structs WHERE user_info_struct_sequence >= 1 ORDER BY field_display_type DESC LIMIT 0,3; |
||||
/*想要先切片在排序,需要写子查询*/ |
||||
SELECT column1, column2 |
||||
FROM ( |
||||
SELECT column1, column2 |
||||
FROM table_name |
||||
LIMIT 10 |
||||
) AS subquery |
||||
ORDER BY column1; |
||||
``` |
||||
## 二、比较逻辑运算 |
||||
|
||||
### 2.1 AND |
||||
|
||||
```sql |
||||
SELECT * FROM table WHERE age > 13 AND age < 20; |
||||
``` |
||||
|
||||
### 2.2 BETWEEN < > |
||||
```sql |
||||
SELECT * FROM table WHERE age > 13 AND age < 20; |
||||
/*相当于*/ |
||||
SELECT * FROM table WHERE BETWEEN 13 AND 20; |
||||
``` |
||||
### 2.3 OR |
||||
|
||||
```sql |
||||
SELECT * FROM table WHERE age >= 13 OR sex = 1; |
||||
``` |
||||
### 2.4 NULL和'' |
||||
|
||||
```sql |
||||
SELECT * FROM table WHERE sex = ''; |
||||
SELECT * FROM table WHERE sex IS NULL; |
||||
SELECT * FROM table WHERE sex IS NOT NULL; |
||||
``` |
||||
### 2.5 !=,<>,NOT |
||||
|
||||
```sql |
||||
SELECT * FROM table WHERE sex != ''; |
||||
/*相当与*/ |
||||
SELECT * FROM table WHERE sex <> ''; |
||||
``` |
||||
|
||||
## 三、多表连接 |
||||
|
||||
### 3.1 内连接 |
||||
|
||||
```sql |
||||
/*student是学生表,dept是专业表,差学生所有信息,并获取学生的所在专业ID的名称*/ |
||||
SELECT s.*, d.name FROM student s INNER JOIN dept d ON s.did = d.did; |
||||
SELECT s.*, d.name FROM student s JOIN dept d ON s.did = d.did; |
||||
/*不推荐写法*/ |
||||
SELECT s.*, d.name FROM student s , dept d WHERE s.did = d.did; |
||||
``` |
||||
### 3.2 左外连接/右外连接 |
||||
|
||||
```sql |
||||
/*以A表为基准, 查出来的数据A表是满的,*/ |
||||
-- 左连接 |
||||
SELECT a.*,b.* FROM ATABLE a LEFT JOIN BTABLE b ON a.bid = b.id; |
||||
-- 左外连接 |
||||
SELECT a.*,b.* FROM ATABLE a LEFT OUTER JOIN BTABLE b ON a.bid = b.id; |
||||
-- 以b表为基准 |
||||
SELECT a.*,b.* FROM BTABLE b LEFT JOIN ATABLE a ON a.bid = b.id; |
||||
``` |
||||
## 四、子查询 |
||||
|
||||
### 4.1 = |
||||
```sql |
||||
-- 查询和id等于2的列名一样的数据 |
||||
SELECT * |
||||
FROM user_info_structs |
||||
WHERE field_name = ( |
||||
SELECT field_name |
||||
FROM user_info_structs |
||||
WHERE user_info_struct_sequence = 2 |
||||
); |
||||
``` |
||||
|
||||
### 4.2 IN |
||||
```sql |
||||
-- 查列名为生日的id的数据 |
||||
SELECT * |
||||
FROM user_info_structs |
||||
WHERE user_info_struct_sequence IN ( |
||||
SELECT user_info_struct_sequence |
||||
FROM user_info_structs |
||||
WHERE field_name = "生日" |
||||
); |
||||
-- 可以使用NOT IN |
||||
-- IN查询一般效率比较低,建议使用多表连接 |
||||
``` |
@ -0,0 +1,42 @@ |
||||
{ |
||||
"name": "hutao", |
||||
"version": "1.0.0", |
||||
"description": "", |
||||
"main": "app.js", |
||||
"type": "module", |
||||
"scripts": { |
||||
"test": "node -v", |
||||
"start": "node app.js", |
||||
"dev": "nodemon app.js" |
||||
}, |
||||
"imports": { |
||||
"#/*": "./*", |
||||
"#common/*": "./src/common/*", |
||||
"#plugins/*": "./src/plugins/*", |
||||
"#databaseModel/*": "./src/databaseModel/*", |
||||
"#routes/*": "./src/routes/*" |
||||
}, |
||||
"keywords": [], |
||||
"author": "", |
||||
"license": "ISC", |
||||
"devDependencies": { |
||||
"nodemon": "^3.1.0" |
||||
}, |
||||
"dependencies": { |
||||
"@fastify/jwt": "^8.0.0", |
||||
"@fastify/rate-limit": "^9.1.0", |
||||
"@fastify/redis": "^6.1.1", |
||||
"@fastify/sensible": "^5.5.0", |
||||
"ajv": "^8.12.0", |
||||
"ajv-errors": "^3.0.0", |
||||
"chalk": "^5.3.0", |
||||
"config": "^3.3.11", |
||||
"destr": "^2.0.3", |
||||
"fastify": "^4.26.2", |
||||
"fastify-plugin": "^4.5.1", |
||||
"mysql2": "^3.9.2", |
||||
"sequelize": "^6.37.1", |
||||
"winston": "^3.12.0", |
||||
"winston-daily-rotate-file": "^5.0.0" |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,65 @@ |
||||
module.exports = { |
||||
"env": { |
||||
"browser": true, |
||||
"es2021": true, |
||||
"es2022": true, |
||||
"es2023": true, |
||||
}, |
||||
"extends": ["eslint:recommended"], |
||||
"overrides": [ |
||||
{ |
||||
"env": { |
||||
"node": true |
||||
}, |
||||
"files": [ |
||||
".eslintrc.{js,cjs}" |
||||
], |
||||
"parserOptions": { |
||||
"sourceType": "script" |
||||
} |
||||
} |
||||
], |
||||
"parserOptions": { |
||||
"ecmaVersion": "latest", |
||||
"sourceType": "module" |
||||
}, |
||||
"rules": { |
||||
indent: ['error', 4, { "SwitchCase": 1 }], // 用于指定代码缩进的方式,这里配置为使用四个空格进行缩进。 |
||||
// 'linebreak-style': [0, 'error', 'windows'], // 用于指定换行符的风格,这里配置为使用 Windows 风格的换行符(\r\n)。 |
||||
quotes: ['error', 'single'], // 用于指定字符串的引号风格,这里配置为使用单引号作为字符串的引号。 |
||||
semi: ['error', 'always'], //用于指定是否需要在语句末尾添加分号,这里配置为必须始终添加分号。 |
||||
"no-console": 2,//禁止使用console |
||||
"no-const-assign": 2,//禁止修改const声明的变量 |
||||
"no-empty": 2,//块语句中的内容不能为空 |
||||
"no-extra-parens": 2,//禁止非必要的括号 |
||||
"no-extra-semi": 2,//禁止多余的冒号 |
||||
"no-fallthrough": 1,//禁止switch穿透 |
||||
"no-func-assign": 2,//禁止重复的函数声明 |
||||
"no-inline-comments": 2,//禁止行内备注 |
||||
"no-irregular-whitespace": 2,//不能有不规则的空格 |
||||
"no-mixed-spaces-and-tabs": [2, false],//禁止混用tab和空格 |
||||
"no-multi-spaces": 1,//不能用多余的空格 |
||||
"no-multiple-empty-lines": [1, {"max": 2}],//空行最多不能超过2行 |
||||
"no-nested-ternary": 0,//禁止使用嵌套的三目运算 |
||||
"no-redeclare": 2,//禁止重复声明变量 |
||||
"no-shadow": 2,//外部作用域中的变量不能与它所包含的作用域中的变量或参数同名 |
||||
"no-trailing-spaces": 2,//一行结束后面不要有空格 |
||||
"no-unexpected-multiline": 2,//避免多行表达式 |
||||
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],//不能有声明后未被使用的变量或参数 |
||||
"no-use-before-define": 2,//未定义前不能使用 |
||||
"no-var": 2,//禁用var,用let和const代替 |
||||
"arrow-parens": 0,//箭头函数用小括号括起来 |
||||
"array-bracket-spacing": [2, "never"],//是否允许非空数组里面有多余的空格 |
||||
"camelcase": 2,//强制驼峰法命名 |
||||
"comma-style": [2, "last"],//逗号风格,换行时在行首还是行尾 |
||||
"comma-spacing": ["error", {"before": false, "after": true}],//对象字面量中冒号的前后空格 |
||||
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],// 冒号后面有空格 |
||||
"lines-around-comment": 0,//行前/行后备注 |
||||
"array-bracket-spacing": ["error", "always"],// 检查数组字面量中的元素之间的空格。 |
||||
}, |
||||
"globals": { |
||||
global: true, |
||||
Buffer: true, |
||||
process: true |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
{ |
||||
"singleQuote": true, |
||||
"trailingComma": "all", |
||||
"tabWidth": 4 |
||||
} |
@ -0,0 +1,89 @@ |
||||
> 如此美妙的音乐 |
||||
|
||||
# Plan Title |
||||
|
||||
1. [中央控制器-Central Control](#P01 中央控制器) |
||||
- 简易宿主系统控制 |
||||
- 日志控制 |
||||
- 子系统状态控制 |
||||
- Docker操作 |
||||
2. 笔记 |
||||
3. todo |
||||
4. 账本 |
||||
5. 文件 |
||||
6. 书籍管理 |
||||
7. 树洞 |
||||
1. 自己缺点的集合,把自己的问题都记录下来 |
||||
2. 可以针对每一个缺点标签开启记录 |
||||
3. 去年今日 |
||||
4. 曾经的自己 |
||||
|
||||
|
||||
--- |
||||
|
||||
# P01 中央控制器 |
||||
|
||||
## 第一部分 概述 |
||||
|
||||
  此模块旨在建立一个易于操作的底层操作系统(Linux|Debian)和应用子系统的统一管理控制面板,它能够对宿主系统进行一定程度的管理,如:宿主系统资源监控,还能对应用系统进行管理,如:环境布置(包括Docker)、应用系统启停、应用系统日志查看、负载均衡、应用系统部署、应用系统版本控制等操作。 |
||||
|
||||
  此模块的目的是将在Linux系统上的操作指令**简化**成图形化的按钮操作,同时对应用系统的开发模板进行一定的**约束/规范**,这里的约束是指应用系统需要一个中央控制器的配置文件,中央控制器会根据配置文件得知应用系统具有的一些功能和操作。 |
||||
|
||||
## 第二部分 思维采集,脑洞补完计划 |
||||
|
||||
### 2024/01/13 20:20 |
||||
1. 进入控制器的入口很隐蔽,需要特殊的端口,特殊的域名,特殊的标记或操作 |
||||
2. 具有网关的作用,可以一键主宰应用子系统的生命周期及请求周期 |
||||
3. 以地图和ip列表的方式,主动防御,阻止请求 |
||||
4. 能够统计子系统的访问记录 |
||||
5. 能够记录对子系统的所有操作和响应 |
||||
6. 中央控制器自身拥有可升级操作 |
||||
7. 在安装子系统配置环境时,可以选择npm源 |
||||
|
||||
## 第三部分 项目准备 |
||||
|
||||
### 技术选型 |
||||
|
||||
- 后端: |
||||
- 语言: JavaScript |
||||
- 框架: Fastify |
||||
- 前端: |
||||
- React |
||||
- 原生 |
||||
- 开发环境: |
||||
- 操作系统: Windows11 |
||||
- 编辑器: WebStorm |
||||
|
||||
### 开发计划 |
||||
|
||||
- 预计开始时间: 2024/01/13 |
||||
- 开发人员: expressgy |
||||
- 预计开发周期: 2024/01/13 - 2024/01/28 [星期日] |
||||
|
||||
### 实际开发日志 |
||||
|
||||
- 2024/01/13 |
||||
- 1. 项目开始前准备 |
||||
- 2. 环境探测(了解Fastify和内部存储) |
||||
- 2024/01/14 |
||||
- 1. 登陆界面 |
||||
|
||||
- 2024/01/15 |
||||
- 1. - [x] 登陆验证 |
||||
1. - [x] 自定义参数验证AJV,我真强 |
||||
- 2024/01/16 |
||||
- 1. - [x] 引入redis |
||||
- 2. - [x] 了解双token |
||||
- 3. - [x] 听说了PostgreSQL |
||||
|
||||
- 2024/01/17 |
||||
- 1. - [ ] 写一点获取系统信息的东西 |
||||
|
||||
|
||||
|
||||
## 开发设计 |
||||
|
||||
### 登录模块 |
||||
|
||||
|
||||
### |
@ -0,0 +1,184 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: 2160
|
||||
// | @文件描述: app.js -
|
||||
// | @创建时间: 2024-01-13 21:05
|
||||
// | @更新时间: 2024-01-13 21:05
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
// 加载时间工具
|
||||
import '#tools/dateFormate.js'; |
||||
|
||||
// ! 读取ENV
|
||||
const ENV = process.argv[2]?.trim().toLowerCase() || 'production'; |
||||
// ! 是否为开发环境
|
||||
const isDev = ENV == 'development' || ENV == 'dev' || ENV == 'develop'; |
||||
// ! 导入环境标识
|
||||
global.isDev = isDev; |
||||
// ! 加载配置文件
|
||||
import {devConf, proConf} from '#config'; |
||||
// ! 导入配置文件
|
||||
global.conf = global.isDev ? devConf : proConf; |
||||
// * 加载核心框架
|
||||
import Fastify from 'fastify'; |
||||
// ! 加载路由
|
||||
import routes from '#routes/index.js'; |
||||
// ! 获取当前设备IP
|
||||
import getLocalIPv4 from '#tools/getLocalIp.js'; |
||||
// * ejs模板
|
||||
import ejs from 'ejs'; |
||||
// * ajv错误模板
|
||||
import ajvErrors from 'ajv-errors'; |
||||
// ! 自定义Ajv参数验证插件
|
||||
import { isLowerCase, isTrim } from '#plugins/ajv/index.js'; |
||||
|
||||
|
||||
async function start(){ |
||||
// | 创建fastify实例
|
||||
const fastify = new Fastify({ |
||||
logger: { |
||||
level: 'debug', |
||||
serializers: { |
||||
req(request) { |
||||
// 不返回任何东西,实际上就是屏蔽了自动的请求日志
|
||||
return {}; |
||||
}, |
||||
res(reply) { |
||||
// 同理,不返回任何东西以屏蔽响应日志
|
||||
return {}; |
||||
} |
||||
}, |
||||
transport: { |
||||
target: 'pino-pretty', |
||||
options: { |
||||
level: 'error', |
||||
// 这个选项确保输出带颜色
|
||||
colorize: true, |
||||
translateTime: 'yyyy-mm-dd HH:MM:ss', |
||||
// 对象信息打印在一行
|
||||
singleLine: true |
||||
} |
||||
} |
||||
}, |
||||
ajv: { |
||||
customOptions: { |
||||
removeAdditional: true, |
||||
useDefaults: true, |
||||
coerceTypes: true, |
||||
allErrors: true, |
||||
// 允许使用联合模式 严格模式下
|
||||
allowUnionTypes: true |
||||
}, |
||||
|
||||
plugins: [ |
||||
ajvErrors, |
||||
// 这种方式完美解决,开心, 实际上这是一个函数,会传进去ajv实例对象
|
||||
isLowerCase, |
||||
isTrim, |
||||
] |
||||
|
||||
} |
||||
}); |
||||
// | 装饰器示例
|
||||
// @ 配置文件
|
||||
fastify.decorate('conf', global.conf); |
||||
|
||||
fastify.decorate('authenticate', async function(request, reply) { |
||||
try { |
||||
// 校验token,并生成request的user参数
|
||||
await request.jwtVerify(); |
||||
} catch (err) { |
||||
// token校验未通过,记录错误
|
||||
fastify.log.debug({ reqId: request.id, Auth: err.message }); |
||||
reply.code(err.statusCode).send({ |
||||
statusCode: err.statusCode, |
||||
message: err.message, |
||||
error: err.name |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
// | 生命周期
|
||||
// @ 请求拦截
|
||||
fastify.addHook('onRequest', async function (request, reply) { |
||||
return; |
||||
}); |
||||
|
||||
// @ 响应拦截
|
||||
fastify.addHook('onResponse', async function (request, reply){ |
||||
return; |
||||
}); |
||||
|
||||
// @ 发送消息拦截
|
||||
fastify.addHook('onSend', async (request, reply, payload) => { |
||||
// fastify.log.info(reply.getHeader('content-type'));
|
||||
if(reply.statusCode === 200 && !request.url.includes('/api/file') ){ |
||||
return `{"statusCode": 200, "status": "success", "data": ${payload}}`; |
||||
} |
||||
return payload; |
||||
}); |
||||
// @ 错误拦截
|
||||
fastify.setErrorHandler(async (error, request, reply) => { |
||||
// 自定义错误处理逻辑
|
||||
fastify.log.error(error); |
||||
reply |
||||
.code(error.statusCode) |
||||
.send({ |
||||
statusCode: error.statusCode, |
||||
message: error.message, |
||||
error: error.name |
||||
}); |
||||
}); |
||||
|
||||
// | 插件
|
||||
// @ 注册防止恶意请求封ip
|
||||
await fastify.register(import('@fastify/rate-limit'), global.conf.rateLimit); |
||||
|
||||
// @ 注册html模板
|
||||
fastify.register(import('@fastify/view'), { |
||||
engine: {ejs}, |
||||
includeViewExtension: true, |
||||
root: 'views' |
||||
}); |
||||
|
||||
// @ 错误处理工具
|
||||
fastify.register(import('@fastify/sensible')); |
||||
|
||||
// @ 注册redis
|
||||
await fastify.register(import('@fastify/redis'), { |
||||
// 配置文件基础redis配置 host,port,password
|
||||
...global.conf.redis, |
||||
// fastify调用的名称空间
|
||||
namespace: 'db1', |
||||
// 指向的redis数据库0-15
|
||||
db: 1, |
||||
// 4 (IPv4) or 6 (IPv6)
|
||||
family: 4, |
||||
// redis连接中的名字
|
||||
connectionName: global.conf.projectName |
||||
}); |
||||
// fastify.log.info(await fastify.redis.db1.set('name', 'xsx', 'EX', 60));
|
||||
|
||||
// @ 注册jwt
|
||||
fastify.register(import('@fastify/jwt'), global.conf.jwt); |
||||
|
||||
// @ 注册路由
|
||||
await fastify.register(routes); |
||||
|
||||
// | 监听指定端口
|
||||
await fastify.listen(global.conf.listen); |
||||
// | 输出监听地址和端口
|
||||
getLocalIPv4().map(i => isDev && fastify.log.info(`http://${i}:${fastify.conf.listen.port}`)); |
||||
|
||||
|
||||
// console.log(fastify.printPlugins())
|
||||
// console.log(fastify.printRoutes({ commonPrefix: false }));
|
||||
// console.log(fastify.printRoutes({ method: 'GET' }));
|
||||
} |
||||
|
||||
start(); |
@ -0,0 +1,23 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: P01CentralControl
|
||||
// | @文件描述: dev.conf.js -
|
||||
// | @创建时间: 2024-01-13 21:15
|
||||
// | @更新时间: 2024-01-13 21:15
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
import proConf from '#root/config/pro.conf.js'; |
||||
const config = { |
||||
devUser: 'expressgy' |
||||
}; |
||||
Object.keys(proConf).forEach(item => { |
||||
if(!config[item]){ |
||||
config[item] = proConf[item]; |
||||
} |
||||
}); |
||||
|
||||
export default config; |
@ -0,0 +1,18 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: P01CentralControl
|
||||
// | @文件描述: index.js -
|
||||
// | @创建时间: 2024-01-13 21:14
|
||||
// | @更新时间: 2024-01-13 21:14
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
import devConfig from './dev.conf.js'; |
||||
import proConfig from './pro.conf.js'; |
||||
|
||||
export const devConf = devConfig; |
||||
export const proConf = proConfig; |
@ -0,0 +1,55 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: P01CentralControl
|
||||
// | @文件描述: pro.conf.js -
|
||||
// | @创建时间: 2024-01-13 21:15
|
||||
// | @更新时间: 2024-01-13 21:15
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
|
||||
const config = { |
||||
// @ 项目名
|
||||
projectName: 'Plan2160-P01 Central Control', |
||||
// 服务启动监听
|
||||
listen: { |
||||
// @ 监听地址
|
||||
host: '0.0.0.0', |
||||
// @ 监听端口
|
||||
port: 21601, |
||||
}, |
||||
// @ 中央控制器账户密码
|
||||
administrator: { |
||||
username: 'expressgy', |
||||
userpass: '**Hxl@1314521' |
||||
}, |
||||
// @ redis
|
||||
redis: { |
||||
host: '172.16.1.10', |
||||
port: 6379, |
||||
password: 'redispassword' |
||||
}, |
||||
// jwt
|
||||
jwt: { |
||||
// token的加密值
|
||||
secret: 'xadvdgfhga21xabhgnumo;opilkujya8axa21', |
||||
sign: { |
||||
// token过期时间
|
||||
expiresIn: '20m' |
||||
} |
||||
}, |
||||
// 防止恶意访问
|
||||
rateLimit: { |
||||
// 限制每个 IP 地址在时间窗口内最多可以发出的请求数
|
||||
max: 500, |
||||
// '1 second' = '1000':1 秒 | '1 minute' 1 分钟 | '1 hour' 1 小时 | '1 day' 1 天 | '1 week' 1 周
|
||||
timeWindow: '1 day' |
||||
} |
||||
}; |
||||
|
||||
|
||||
export default config; |
@ -0,0 +1,21 @@ |
||||
`backdrop-filter` 是 CSS 的一个属性,用于在元素的背景和元素之间创建一个视觉效果的过滤器。它可以应用多种效果,如模糊、色彩调整和亮度调整等。以下是一些常见的 `backdrop-filter` 属性及其作用与单位: |
||||
|
||||
1. `blur()`: 模糊元素的背景,可以通过指定一个模糊半径来调整模糊程度。单位为像素(px)。 |
||||
|
||||
2. `brightness()`: 调整元素的背景亮度,可以通过指定一个百分比或小数值来控制亮度增减。单位为百分比(%)或小数。 |
||||
|
||||
3. `contrast()`: 调整元素的背景对比度,可以通过指定一个百分比或小数值来增加或减小对比度。单位为百分比(%)或小数。 |
||||
|
||||
4. `grayscale()`: 将元素的背景转为灰度图像,可以通过指定一个百分比或小数值来控制转换程度。单位为百分比(%)或小数。 |
||||
|
||||
5. `hue-rotate()`: 调整元素的背景颜色的色调,可以通过指定一个角度值来旋转颜色。单位为角度(deg)。 |
||||
|
||||
6. `invert()`: 反转元素的背景颜色,即颜色取反。无单位。 |
||||
|
||||
7. `opacity()`: 调整元素的背景透明度,可以通过指定一个百分比或小数值来控制透明度。单位为百分比(%)或小数。 |
||||
|
||||
8. `saturate()`: 调整元素的背景饱和度,可以通过指定一个百分比或小数值来增加或减小饱和度。单位为百分比(%)或小数。 |
||||
|
||||
9. `sepia()`: 将元素的背景转为深褐色调,可以通过指定一个百分比或小数值来控制转换程度。单位为百分比(%)或小数。 |
||||
|
||||
这些 `backdrop-filter` 属性可以结合使用,以创建具有复合效果的背景过滤器。请注意,`backdrop-filter` 属性需要一些现代 Web 浏览器的支持,且可能不支持所有旧版浏览器。在使用时,请确保检查浏览器的兼容性。 |
@ -0,0 +1,24 @@ |
||||
在 Fastify 框架中,`request` 和 `reply` 是对 HTTP 请求和响应的封装,并传递给每个路由处理函数。它们分别提供了丰富的 API 来处理传入的请求和构造回去的响应。 |
||||
|
||||
1. **request**: 表示一个传入的 HTTP 请求。它具有多个属性和方法,用来访问请求的内容,例如: |
||||
|
||||
- `request.body`: 包含了 POST、PATCH 或 PUT 请求中的 payload。 |
||||
- `request.query`: 包含了 URL 查询字符串的键值对。 |
||||
- `request.params`: 包含了路由参数。 |
||||
- `request.headers`: 包含了请求头的键值对。 |
||||
- `request.raw`: Fastify 为了性能考虑并不会对 Node.js 原生的 `request` 对象进行重量级的包装,所以 `request.raw` 就是原始的 Node.js HTTP 请求对象。 |
||||
- `request.log`: 用于记录日志的 logger 实例。 |
||||
|
||||
此外,还有很多其他的属性和方法,你可以用来操作和获取有关请求的其他细节。 |
||||
|
||||
2. **reply**: 表示对于客户端请求的响应。它封装了多个定义和发送 HTTP 响应的方法,例如: |
||||
|
||||
- `reply.code(statusCode)`: 设置 HTTP 响应的状态代码。 |
||||
- `reply.header(name, value)`: 设置 HTTP 响应头。 |
||||
- `reply.send(payload)`: 发送响应数据到客户端,这里的 payload 可以是一个字符串、Buffer、对象等。 |
||||
- `reply.type(contentType)`: 简便方法设置 `Content-Type` 响应头。 |
||||
- `reply.raw`: 和 `request.raw` 类似,`reply.raw` 就是原始的 Node.js HTTP 响应对象。 |
||||
|
||||
就像 `request` 一样,`reply` 提供了其他的方法和属性来处理 HTTP 响应。 |
||||
|
||||
这些封装提供了快捷的方法操作请求和响应,而不必深入了解底层的 Node.js HTTP API。Fastify 的设计也为高性能优化,避免不必要的封装和抽象。 |
@ -0,0 +1,191 @@ |
||||
在使用 JSON Schema(如在 Fastify 或其他支持 JSON Schema 验证的系统中)时,`type` 关键字定义了数据类型。以下是一般支持的数据类型列表: |
||||
|
||||
1. **string**:表示字符串。还可以配合 `format` 代表特定格式的字符串,如 `date-time`, `email`, `hostname`, `ipv4`, `ipv6`, `uri`, 等。 |
||||
|
||||
2. **number**:表示任意数值,包括浮点数。 |
||||
|
||||
3. **integer**:表示整数。 |
||||
|
||||
4. **boolean**:表示布尔值 true 或 false。 |
||||
|
||||
5. **object**:表示关键字和值的集合,关键字必须为字符串。可以进一步描述对象的属性、所需的属性、属性的依赖性等。 |
||||
|
||||
6. **array**:表示一组有序的元素(项),项可以是任何类型。可以进一步定义项类型、数组长度等。 |
||||
|
||||
7. **null**:表示 null 值。 |
||||
|
||||
JSON Schema 还支持复合类型,例如: |
||||
|
||||
- 使用 **enums** 定义一组固定的值。 |
||||
- 使用 `oneOf`, `anyOf`, `allOf`, 和 `not` 关键字定义更复杂的逻辑组合。 |
||||
|
||||
例如,如果你希望属性接受字符串或 null 两种类型,可以使用 `type` 关键字的数组形式: |
||||
|
||||
```json |
||||
{ |
||||
"type": ["string", "null"] |
||||
} |
||||
``` |
||||
|
||||
需要注意的是,除了上述的基本数据类型,JSON Schema 还支持定义更细致的限制条件,比如: |
||||
|
||||
- 对于字符串:`minLength`, `maxLength`, 和正则表达式 `pattern` |
||||
- 对于数值:`minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf` |
||||
- 对于对象:`properties`, `additionalProperties`, `required`, `patternProperties`, `minProperties`, `maxProperties` |
||||
- 对于数组:`items`, `additionalItems`, `minItems`, `maxItems`, `uniqueItems`, `contains` |
||||
|
||||
还可以通过 `default`, `examples`, 和 `const` 等关键字提供更多信息。 |
||||
|
||||
上述列表涵盖了 JSON Schema Draft-07 和之后版本中的常见数据类型和功能。根据具体实施和使用的 JSON Schema 草案版本(Draft),可能会有细微差别。确保查看你使用的 JSON Schema 草案版本文档以获取完整信息。 |
||||
|
||||
|
||||
|
||||
--- |
||||
|
||||
`oneOf`,`anyOf`,`allOf` 和 `not` 是 JSON Schema 中用于组合其他规则的关键字。它们允许您对多个条件进行逻辑运算,以更精细地定义数据的结构。 |
||||
|
||||
- `oneOf`: 表示只有一个架构应匹配数据。如果多个架构都匹配数据,验证将失败。这类似于 "XOR" 的逻辑关系。 |
||||
|
||||
示例: |
||||
```json |
||||
{ |
||||
"oneOf": [ |
||||
{ "type": "string" }, |
||||
{ "maxLength": 5 } |
||||
] |
||||
} |
||||
``` |
||||
这个示例声明了数据可以是一个字符串,或者长度最大为 5 的字符串。如果数据不是字符串,或者长度超过 5,验证将失败。 |
||||
|
||||
- `anyOf`: 表示至少有一个架构应匹配数据。只有当至少一个架构满足条件时,验证才会通过。 |
||||
|
||||
示例: |
||||
```json |
||||
{ |
||||
"anyOf": [ |
||||
{ "type": "string" }, |
||||
{ "maxLength": 5 } |
||||
] |
||||
} |
||||
``` |
||||
这个示例声明了数据可以是一个字符串,或者长度最大为 5 的字符串。只要满足其中一个条件,即可通过验证。 |
||||
|
||||
- `allOf`: 表示所有架构都必须匹配数据。只有当所有架构条件都满足时,验证才会通过。 |
||||
|
||||
示例: |
||||
```json |
||||
{ |
||||
"allOf": [ |
||||
{ "type": "string" }, |
||||
{ "minLength": 2 }, |
||||
{ "maxLength": 5 } |
||||
] |
||||
} |
||||
``` |
||||
这个示例要求数据必须是一个长度介于 2 和 5 之间的字符串,同时满足三个条件。 |
||||
|
||||
- `not`: 表示数据不能匹配给定的架构。如果数据满足 `not` 中描述的架构,则验证将失败。 |
||||
|
||||
示例: |
||||
```json |
||||
{ |
||||
"not": { |
||||
"type": "null" |
||||
} |
||||
} |
||||
``` |
||||
这个示例要求数据不能是 `null`。如果数据是 `null`,则验证将失败。 |
||||
|
||||
这些组合关键字能够根据特定的需求对数据施加更复杂的约束和规则。您可以根据您的条件使用适当的组合关键字来定义数据的结构和验证规则。 |
||||
|
||||
|
||||
--- |
||||
|
||||
在 JSON Schema 中,当你在定义数组类型的时候,有几个关键字可以用来更精确地描述数组的结构和约束: |
||||
|
||||
- `items`: 用来指定数组中每个元素的架构。它可以是单个架构,适用于数组中的所有元素,或者是一个架构数组,适用于数组中每个位置的元素。 |
||||
|
||||
示例(所有元素相同架构): |
||||
```json |
||||
{ |
||||
"type": "array", |
||||
"items": { "type": "string" } |
||||
} |
||||
``` |
||||
在这个例子中,数组中的所有元素都必须是字符串。 |
||||
|
||||
示例(特定位置架构): |
||||
```json |
||||
{ |
||||
"type": "array", |
||||
"items": [ |
||||
{ "type": "number" }, |
||||
{ "type": "string" } |
||||
] |
||||
} |
||||
``` |
||||
在这个例子中,数组的第一个元素必须是数字,第二个元素必须是字符串。 |
||||
|
||||
- `additionalItems`: 当使用架构数组定义 `items` 时,`additionalItems` 关键字规定了数组中额外元素的架构(即超出 `items`数组定义的元素的架构)。 |
||||
|
||||
示例: |
||||
```json |
||||
{ |
||||
"type": "array", |
||||
"items": [ |
||||
{ "type": "number" }, |
||||
{ "type": "string" } |
||||
], |
||||
"additionalItems": { "type": "string" } |
||||
} |
||||
``` |
||||
在这个例子中,第三个及以后的元素都必须是字符串。 |
||||
|
||||
- `minItems`: 指定数组的最小长度。 |
||||
|
||||
示例: |
||||
```json |
||||
{ |
||||
"type": "array", |
||||
"minItems": 2 |
||||
} |
||||
``` |
||||
数组至少要有两个元素。 |
||||
|
||||
- `maxItems`: 指定数组的最大长度。 |
||||
|
||||
示例: |
||||
```json |
||||
{ |
||||
"type": "array", |
||||
"maxItems": 5 |
||||
} |
||||
``` |
||||
数组最多可以有五个元素。 |
||||
|
||||
- `uniqueItems`: 要求数组中的所有元素都是唯一的。如果设置为 `true`,则不允许有重复的元素。 |
||||
|
||||
示例: |
||||
```json |
||||
{ |
||||
"type": "array", |
||||
"uniqueItems": true |
||||
} |
||||
``` |
||||
所有的元素应该都是独一无二的。 |
||||
|
||||
- `contains`: 确保数组至少包含一个符合提供的架构的元素。 |
||||
|
||||
示例: |
||||
```json |
||||
{ |
||||
"type": "array", |
||||
"contains": { |
||||
"type": "string", |
||||
"pattern": "^a" |
||||
} |
||||
} |
||||
``` |
||||
这个架构要求数组至少包含一个以字母“a”开头的字符串。 |
||||
|
||||
这些关键字可以单独使用,也可以组合使用,以便根据具体的需要为数组数据结构定义详细的限制和规则。 |
@ -0,0 +1,48 @@ |
||||
{ |
||||
"name": "p01centralcontrol", |
||||
"version": "1.0.0", |
||||
"description": "", |
||||
"main": "app.js", |
||||
"type": "module", |
||||
"directories": { |
||||
"test": "test" |
||||
}, |
||||
"imports": { |
||||
"#root/*": "./*", |
||||
"#config": "./config/index.js", |
||||
"#tools/*": "./tools/*", |
||||
"#routes/*": "./routes/*", |
||||
"#plugins/*": "./plugins/*", |
||||
"#task/*": "./src/task/*", |
||||
"#workers/*": "./src/workers/*", |
||||
"#protocol/*": "./src/protocol/*", |
||||
"#processes/*": "./src/processes/*" |
||||
}, |
||||
"scripts": { |
||||
"test": "echo \"Error: no test specified\" && exit 1", |
||||
"start": "node app.js production", |
||||
"dev": "nodemon app.js dev" |
||||
}, |
||||
"keywords": [], |
||||
"author": "", |
||||
"license": "ISC", |
||||
"dependencies": { |
||||
"@fastify/jwt": "^8.0.0", |
||||
"@fastify/rate-limit": "^9.1.0", |
||||
"@fastify/redis": "^6.1.1", |
||||
"@fastify/sensible": "^5.5.0", |
||||
"@fastify/view": "^8.2.0", |
||||
"ajv": "^8.12.0", |
||||
"ajv-errors": "^3.0.0", |
||||
"ejs": "^3.1.9", |
||||
"fastify": "^4.25.2" |
||||
}, |
||||
"devDependencies": { |
||||
"eslint": "^8.52.0", |
||||
"eslint-config-prettier": "^9.0.0", |
||||
"eslint-plugin-prettier": "^5.0.0", |
||||
"nodemon": "^3.0.2", |
||||
"pino-pretty": "^10.3.1", |
||||
"prettier": "^3.0.3" |
||||
} |
||||
} |
@ -0,0 +1,82 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: P01CentralControl
|
||||
// | @文件描述: index.js -
|
||||
// | @创建时间: 2024-01-15 10:49
|
||||
// | @更新时间: 2024-01-15 10:49
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
import Ajv from 'ajv'; |
||||
// ajv错误消息回应
|
||||
import ajvErrors from 'ajv-errors'; |
||||
|
||||
|
||||
|
||||
// 将字符串转化为全小写
|
||||
export function isLowerCase(ajv){ |
||||
ajv.addKeyword({ |
||||
keyword: 'isLowerCase', |
||||
// 开启错误收集
|
||||
errors: true, |
||||
modifying: true, |
||||
validate: function validateIsEven(schema, data, parentSchema, dataCxt) { |
||||
if(typeof data == 'string'){ |
||||
dataCxt.parentData[dataCxt.parentDataProperty] = dataCxt.parentData[dataCxt.parentDataProperty].trim().toLowerCase(); |
||||
return true; |
||||
}else{ |
||||
validateIsEven.errors = [ { |
||||
message: '错误的数据' |
||||
} ]; |
||||
return false; |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
// 去除两端空格
|
||||
export function isTrim(ajv){ |
||||
ajv.addKeyword({ |
||||
keyword: 'isTrim', |
||||
type: 'string', |
||||
// 开启错误收集
|
||||
errors: true, |
||||
modifying: true, |
||||
validate: function validateIsEven(schema, data, parentSchema, dataCxt) { |
||||
if(typeof data == 'string'){ |
||||
dataCxt.parentData[dataCxt.parentDataProperty] = dataCxt.parentData[dataCxt.parentDataProperty].trim(); |
||||
return true; |
||||
}else{ |
||||
validateIsEven.errors = [ { |
||||
message: 'is note String' |
||||
} ]; |
||||
return false; |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
|
||||
|
||||
// 给fastify添加自定义的参数校验规则
|
||||
function customAjv(fastify, options){ |
||||
// 创建一个新的 AJV 实例
|
||||
// 创建一个新的 AJV 实例
|
||||
const ajv = new Ajv({ |
||||
removeAdditional: true, |
||||
useDefaults: true, |
||||
coerceTypes: true, |
||||
// jsonPointers: true,
|
||||
allErrors: true |
||||
}); |
||||
ajvErrors(ajv); |
||||
isLowerCase(ajv); |
||||
// 使用自定义的 AJV 实例为 Fastify 设置验证器编译器
|
||||
fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { |
||||
return ajv.compile(schema); |
||||
}); |
||||
} |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,183 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: fastify-template
|
||||
// | @文件描述: index.js -
|
||||
// | @创建时间: 2023-12-16 22:41
|
||||
// | @更新时间: 2023-12-16 22:41
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
import user from './user/index.js'; |
||||
import system from './system/index.js'; |
||||
function rootRoute(fastify, options, done){ |
||||
fastify.decorate('projectName', fastify.conf.projectName); |
||||
fastify.get('/', async function (request, reply){ |
||||
return { |
||||
path: request.url, |
||||
timestamp: new Date().getZH() |
||||
}; |
||||
}); |
||||
const mySchema = { |
||||
type: 'object', |
||||
properties: { |
||||
name: { |
||||
type: 'string' |
||||
}, |
||||
age: { |
||||
type: 'integer' |
||||
}, |
||||
love: { |
||||
type: 'string' |
||||
} |
||||
}, |
||||
required: [ 'name', 'age' ] |
||||
}; |
||||
const mySchema2 = { |
||||
body: { |
||||
type: 'object', |
||||
additionalProperties: false, // 不允许读取
|
||||
required: [ 'string' ], // 必填
|
||||
minProperties: 1, // 最小属性数量
|
||||
maxProperties: 25, // 最大属性数量
|
||||
properties: { |
||||
array: { |
||||
type: 'array', |
||||
minItems: 2, |
||||
maxItems: 2, |
||||
uniqueItems: true, // 元素唯一
|
||||
items: [ |
||||
{ type: 'number' }, |
||||
{ type: 'string' } |
||||
], // 数组的第一个元素必须是数字,第二个元素必须是字符串。
|
||||
// items: {
|
||||
// anyOf: [
|
||||
// { type: 'number' },
|
||||
// { type: 'string' }
|
||||
// ]
|
||||
// }, // 数组任意位置都可以是number和string
|
||||
// additionalItems: {
|
||||
// anyOf: [
|
||||
// { type: 'number' },
|
||||
// { type: 'string' }
|
||||
// ]
|
||||
// }, // items定义之外的元素,必须定义items
|
||||
errorMessage: { |
||||
minItems: '数组长度不能小于2', |
||||
maxItems: '数组长度不能大于5' |
||||
} |
||||
}, |
||||
string: { |
||||
type: 'string', |
||||
maxLength: 200, |
||||
minLength: 1, |
||||
// pattern: '', // 支持正则表达式
|
||||
errorMessage: { |
||||
type: 'string必须为字符串', |
||||
maxLength: '字符长度不能大于20', |
||||
minLength: '字符长度不能小于10' |
||||
} |
||||
}, |
||||
number: { |
||||
type: 'number', |
||||
maximum: 2000, |
||||
minimum: 1, |
||||
// multipleOf: 3, // 必须是3的倍数
|
||||
errorMessage: { |
||||
type: 'string必须为数字', |
||||
maximum: '数字大小最大为20', |
||||
minimum: '数字大小最小为10' |
||||
} |
||||
}, |
||||
integer: { |
||||
type: 'integer', |
||||
exclusiveMaximum: 20, |
||||
exclusiveMinimum: 2, |
||||
errorMessage: { |
||||
type: 'string必须为数字', |
||||
exclusiveMaximum: '数字大小最大为19', |
||||
exclusiveMinimum: '数字大小最小为11' |
||||
} |
||||
}, |
||||
enum1: { |
||||
type: 'number', |
||||
enum: [ 1, 2, 3 ] |
||||
}, |
||||
enum2: { |
||||
type: [ 'number', 'string' ], |
||||
enum: [ 1, 2, 'xs' ], |
||||
errorMessage: { |
||||
enum: '不在枚举范围内', |
||||
} |
||||
}, |
||||
enum3: { |
||||
oneOf: [ |
||||
{ 'type': 'string', 'enum': [ 'temp' ] }, |
||||
{ 'type': 'number', 'enum': [ 25 ] }, |
||||
{ 'type': 'boolean', 'enum': [ true, false ] } |
||||
], |
||||
errorMessage: { |
||||
enum: '不在枚举范围内', |
||||
} |
||||
}, |
||||
boolean: { |
||||
type: 'boolean' |
||||
}, |
||||
// xsx: 'number',
|
||||
studentList: { |
||||
type: 'array', |
||||
items: mySchema |
||||
} |
||||
} |
||||
}, |
||||
response: { |
||||
200: { |
||||
type: 'object', |
||||
properties: { |
||||
hello: { type: 'string' } |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
fastify.route({ |
||||
url: '/testSchema', |
||||
method: 'POST', |
||||
schema: mySchema2, |
||||
handler: async function(request, reply){ |
||||
console.log(request.body); |
||||
return 'A'; |
||||
} |
||||
}); |
||||
fastify.post('/testSchema2', {schema: mySchema2}, async function(response, reply){ |
||||
return 'A'; |
||||
}); |
||||
|
||||
fastify.register(user, { |
||||
prefix: 'user' |
||||
}); |
||||
|
||||
fastify.register(system, { |
||||
prefix: 'system' |
||||
}); |
||||
|
||||
done(); |
||||
} |
||||
|
||||
export default function routes(fastify, option, done){ |
||||
fastify.get('/', async function(request, reply){ |
||||
|
||||
return reply.view('/index.pug', { title: this.conf.projectName }); |
||||
// return {
|
||||
// project: fastify.conf.projectName,
|
||||
// framework: 'Fastify ' + fastify.version,
|
||||
// timestamp: new Date().getZH()
|
||||
// };
|
||||
}); |
||||
fastify.register(rootRoute, { |
||||
prefix: '/api' |
||||
}); |
||||
done(); |
||||
} |
@ -0,0 +1,24 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: P01CentralControl
|
||||
// | @文件描述: index.js -
|
||||
// | @创建时间: 2024-01-16 15:23
|
||||
// | @更新时间: 2024-01-16 15:23
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
export default function (fastify, options, done){ |
||||
// @ 获取系统信息
|
||||
fastify.get('/getInfo', { |
||||
preValidation: [ fastify.authenticate ] |
||||
}, async function(request, reply){ |
||||
this.log.error(request); |
||||
this.log.warn(request.user); |
||||
return 'ok'; |
||||
}); |
||||
done(); |
||||
} |
||||
|
@ -0,0 +1,55 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: fastify-template
|
||||
// | @文件描述: index.js -
|
||||
// | @创建时间: 2023-12-16 22:52
|
||||
// | @更新时间: 2023-12-16 22:52
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
export default function (fastify, options, done){ |
||||
// @ 登录
|
||||
fastify.post('/signin', { |
||||
schema: { |
||||
body: { |
||||
type: 'object', |
||||
required: [ 'username', 'userpass' ], |
||||
properties: { |
||||
username: { |
||||
type: 'string', |
||||
isLowerCase: true, |
||||
errorMessage: { |
||||
type: 'username必须为字符串', |
||||
// isEven: 'isEven'
|
||||
}, |
||||
}, |
||||
userpass: { |
||||
type: 'string', |
||||
isTrim: true, |
||||
}, |
||||
code: { |
||||
type: 'integer' |
||||
} |
||||
}, |
||||
errorMessage: { |
||||
required: { |
||||
username: 'username为必填项' |
||||
} |
||||
} |
||||
|
||||
} |
||||
}, |
||||
}, async function(request, reply){ |
||||
const {username, userpass} = request.body; |
||||
if(username != this.conf.administrator.username || userpass != this.conf.administrator.userpass){ |
||||
return fastify.httpErrors.conflict('用户名或密码错误!'); |
||||
} |
||||
// 生成token
|
||||
const token = fastify.jwt.sign({ payload: {username: 'expressgy'}, timestamp: new Date().getZH() }); |
||||
return { token }; |
||||
}); |
||||
done(); |
||||
} |
@ -0,0 +1,52 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<title>Title</title> |
||||
<style> |
||||
html, body { |
||||
margin: 0; |
||||
padding: 0; |
||||
height: 100%; |
||||
width: 100%; |
||||
background: #000; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
color: #d11a2dcc; |
||||
font-size: 18px; |
||||
font-weight: 100; |
||||
font-family: "方正粗黑宋简体"; |
||||
text-align: center; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<div> |
||||
<div> |
||||
<svg style="width: 100px;height: 80px" t="1705482549300" class="icon" viewBox="0 0 1024 1024" version="1.1" |
||||
xmlns="http://www.w3.org/2000/svg" p-id="11922" width="2048" height="2048"> |
||||
<path |
||||
d="M957.025121 512.176009c0.107447-18.962887-7.498788-32.92897-7.93267-33.471322-60.314691-115.142413-172.684968-189.040471-177.357384-192.029548-80.529081-52.978609-168.012551-79.930447-259.626086-80.093153l0-0.597611c-91.830475 0.162706-179.206498 27.060309-259.842003 80.148411-4.564969 2.988054-117.151164 76.887135-177.248913 191.975313-0.434905 0.597611-8.150634 14.508435-8.042163 33.471322l-0.10847 0c-0.216941 19.126616 7.607258 33.200146 8.042163 33.744545 60.20622 115.14139 172.684968 189.039447 177.248913 192.028525 80.637552 53.061496 168.012551 79.903841 259.733533 80.120782l0 0.543376c91.831498-0.10847 179.313945-27.060309 259.951497-80.120782 4.672416-2.989077 117.042693-76.888158 177.357384-191.975313 0.433882-0.597611 8.150634-14.616906 7.93267-33.744545L957.025121 512.176009zM512.000512 678.939103c-92.15691 0-166.926824-74.714655-166.926824-166.9258 0-92.210122 74.768891-166.924777 166.926824-166.924777 92.263333 0 166.924777 74.714655 166.924777 166.924777C678.925289 604.224448 604.263845 678.939103 512.000512 678.939103z" |
||||
fill="#ccc" p-id="11923"></path> |
||||
</svg> |
||||
</div> |
||||
<div>老当益壮 宁移白首之心 穷且益坚 不坠青云之志</div> |
||||
<div>明日复明日 明日何其多 我生待明日 万事成蹉跎</div> |
||||
<div>严肃认真 周到细致 稳妥可靠 万无一失</div> |
||||
<div>天与弗取 反受其咎 时至不行 反受其殃</div> |
||||
<div>吾之所向 一往无前 愈挫愈奋 再接再厉</div> |
||||
<div>待到秋来九月八 我花开后百花杀</div> |
||||
<div>酌贪泉而觉爽 处涸辙以犹欢</div> |
||||
<div>知不可乎骤得 托遗响于悲风</div> |
||||
<div>实迷途其未远 觉今是而昨非</div> |
||||
<div style="margin: 10px 0; color: #666"> |
||||
<div>滕王高阁临江渚 佩玉鸣鸾罢歌舞</div> |
||||
<div>画栋朝飞南浦云 珠帘暮卷西山雨</div> |
||||
<div>闲云潭影日悠悠 物换星移几度秋</div> |
||||
<div>阁中帝子今何在 槛外长江空自流</div> |
||||
</div> |
||||
<div>己所不欲 勿施于人</div> |
||||
</div> |
||||
</body> |
||||
</html> |
@ -0,0 +1,163 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: 2160
|
||||
// | @文件描述: homePage.js -
|
||||
// | @创建时间: 2024-01-13 20:57
|
||||
// | @更新时间: 2024-01-13 20:57
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
window.onload = () => { |
||||
const body = document.body; |
||||
let username, password |
||||
|
||||
// 开局提示
|
||||
const welcomeElement = document.createElement('div'); |
||||
body.append(welcomeElement); |
||||
const welcomeText = `Welcome To XY.\nYou Are About To Enter The Top Management Interface.`; |
||||
renderText(welcomeText, welcomeElement, () => { |
||||
// 询问是否继续
|
||||
const askElement = document.createElement('div'); |
||||
body.append(askElement); |
||||
const askText = 'Can Verify Your Identity Now? (Yes / No):' |
||||
renderText(askText, askElement, () => { |
||||
// 确认输入
|
||||
const answerElement = document.createElement('div'); |
||||
body.append(answerElement); |
||||
inputText(answerElement, text => { |
||||
const answer = text.toLowerCase() |
||||
if(answer == 'y' || answer == 'yes'){ |
||||
// 用户名提示
|
||||
const userText = 'USERNAME: ' |
||||
const usernameElement = document.createElement('div'); |
||||
const userHeaderElement = document.createElement('span'); |
||||
const userBodyElement = document.createElement('span'); |
||||
body.append(usernameElement) |
||||
usernameElement.append(userHeaderElement) |
||||
usernameElement.append(userBodyElement) |
||||
renderText(userText, userHeaderElement, () => { |
||||
inputText(userBodyElement, usernameText => { |
||||
username = usernameText |
||||
// 密码提示
|
||||
const passwdText = 'PASSWORD: ' |
||||
const passwdElement = document.createElement('div'); |
||||
const passwdHeaderElement = document.createElement('span'); |
||||
const passwdBodyElement = document.createElement('span'); |
||||
body.append(passwdElement) |
||||
passwdElement.append(passwdHeaderElement) |
||||
passwdElement.append(passwdBodyElement) |
||||
renderText(passwdText, passwdHeaderElement, () => { |
||||
inputText(passwdBodyElement, passwdText => { |
||||
password = passwdText |
||||
Entry() |
||||
}, null, true) |
||||
}, 0, 0) |
||||
}, null) |
||||
}, 0, 0) |
||||
}else{ |
||||
setTimeout(() => { |
||||
window.close() |
||||
}, 3000) |
||||
console.log('Window On Close in 3000ms!') |
||||
return; |
||||
} |
||||
}, true) |
||||
}, 0, 0) |
||||
}) |
||||
} |
||||
|
||||
function renderText(text, element, callback, startWait = 2000, endWait= 1000){ |
||||
const textElement = document.createElement('span'); |
||||
const cursorElement = document.createElement('span'); |
||||
cursorElement.classList.add('welecomeTextCursor'); |
||||
cursorElement.innerHTML = ' ' |
||||
|
||||
element.append(textElement, cursorElement) |
||||
const max = text.length |
||||
let nowLetter = 1 |
||||
setTimeout(() => { |
||||
const setTextInterval = setInterval(() => { |
||||
const spanElement = document.createElement('span'); |
||||
const nowText = text[nowLetter - 1] |
||||
if(nowText == ' '){ |
||||
spanElement.innerHTML = ' ' |
||||
}else{ |
||||
spanElement.innerText = nowText |
||||
} |
||||
|
||||
spanElement.classList.add('welecomeText'); |
||||
textElement.append(spanElement) |
||||
if(nowLetter == max){ |
||||
clearInterval(setTextInterval) |
||||
setTimeout(() => { |
||||
cursorElement.remove() |
||||
callback() |
||||
}, endWait) |
||||
}else{ |
||||
nowLetter++ |
||||
} |
||||
}, 30) |
||||
}, startWait) |
||||
|
||||
} |
||||
|
||||
function inputText(element, callback, letter, passwd){ |
||||
const textElement = document.createElement('span'); |
||||
const cursorElement = document.createElement('span'); |
||||
cursorElement.classList.add('inputTextCursor'); |
||||
cursorElement.innerHTML = ' '; |
||||
|
||||
element.append(textElement, cursorElement) |
||||
|
||||
const store = { |
||||
key: '', |
||||
string: '' |
||||
} |
||||
|
||||
document.body.addEventListener('keyup', getKeyWord) |
||||
if(!letter){ |
||||
letter = 'QWERTYUIOPASDFGHJKLZXCVBNM1234567890*@#&' + 'QWERTYUIOPASDFGHJKLZXCVBNM'.toLowerCase().split('') |
||||
}else{ |
||||
letter = ['Y','y','E','e','S','s','N','n','O','o'] |
||||
} |
||||
function getKeyWord(){ |
||||
if(letter && letter.includes(event.key)){ |
||||
store.key = event.key; |
||||
store.string = store.string + event.key |
||||
} |
||||
if(event.key == 'Backspace'){ |
||||
store.string = store.string.slice(0, store.string.length - 1) |
||||
} |
||||
if(event.key == 'Enter'){ |
||||
if(store.string.length==0){ |
||||
|
||||
}else{ |
||||
cursorElement.remove() |
||||
callback(store.string) |
||||
document.body.removeEventListener('keyup', getKeyWord) |
||||
} |
||||
} |
||||
if(passwd){ |
||||
textElement.innerText = new Array(store.string.length).fill('*').join('') |
||||
}else{ |
||||
textElement.innerText = store.string |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
function Entry(){ |
||||
if(true){ |
||||
const entryMessageElement = document.createElement('div') |
||||
document.body.append(entryMessageElement) |
||||
renderText('About To Enter The System!', entryMessageElement, () => { |
||||
const loadingElement = document.createElement('div') |
||||
loadingElement.classList.add('loading') |
||||
document.body.append(loadingElement) |
||||
}, 0, 0) |
||||
|
||||
} |
||||
} |
@ -0,0 +1,41 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: P01CentralControl
|
||||
// | @文件描述: dateFormate.js -
|
||||
// | @创建时间: 2024-01-13 21:48
|
||||
// | @更新时间: 2024-01-13 21:48
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
function formateDate(){ |
||||
Date.prototype.format = function (format) { |
||||
let args = { |
||||
'M+': this.getMonth() + 1, |
||||
'd+': this.getDate(), |
||||
'h+': this.getHours(), |
||||
'm+': this.getMinutes(), |
||||
's+': this.getSeconds(), |
||||
'q+': Math.floor((this.getMonth() + 3) / 3), //quarter
|
||||
'S': this.getMilliseconds() |
||||
}; |
||||
if (/(y+)/.test(format)) |
||||
format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length)); |
||||
for (let i in args) { |
||||
let n = args[i]; |
||||
if (new RegExp('(' + i + ')').test(format)) |
||||
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? n : ('00' + n).substr(('' + n).length)); |
||||
} |
||||
return format; |
||||
}; |
||||
Date.prototype.getZH = function(){ |
||||
return `${this.getFullYear()}-${this.getMonth()}-${this.getDate()} ${this.getHours()}:${this.getMinutes()}:${this.getSeconds()}`; |
||||
}; |
||||
Date.prototype.getZHS = function(){ |
||||
return `${this.getFullYear()}-${this.getMonth()}-${this.getDate()} ${this.getHours()}:${this.getMinutes()}:${this.getSeconds()}:${this.getMilliseconds()}`; |
||||
}; |
||||
} |
||||
formateDate(); |
@ -0,0 +1,26 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: P01CentralControl
|
||||
// | @文件描述: getLocalIp.js -
|
||||
// | @创建时间: 2024-01-14 01:07
|
||||
// | @更新时间: 2024-01-14 01:07
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
import os from 'os'; |
||||
export default function getLocalIPv4() { |
||||
const networkInterfaces = os.networkInterfaces(); |
||||
const ipList = []; |
||||
for (const interfaceKey of Object.keys(networkInterfaces)) { |
||||
const addresses = networkInterfaces[interfaceKey]; |
||||
for (const address of addresses) { |
||||
if (address.family === 'IPv4' && !address.internal) { |
||||
ipList.push(address.address); |
||||
} |
||||
} |
||||
} |
||||
return ipList; |
||||
} |
@ -0,0 +1,268 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="zh"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<title><%= title %></title> |
||||
<style> |
||||
html, body { |
||||
position: relative; |
||||
height: 100%; |
||||
width: 100%; |
||||
overflow: hidden; |
||||
margin: 0; |
||||
padding: 0; |
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; |
||||
background: #000; |
||||
font-family: Consolas, NotoSans; |
||||
user-select: none; |
||||
} |
||||
body{ |
||||
padding: 10px; |
||||
box-sizing: border-box; |
||||
} |
||||
span{ |
||||
color: #46F372; |
||||
} |
||||
i{ |
||||
color: #46F372; |
||||
font-weight: bold; |
||||
} |
||||
.welecomeText{ |
||||
position: relative; |
||||
line-height: 1.3em; |
||||
} |
||||
.welecomeTextCursor{ |
||||
position: relative; |
||||
background: #46F372; |
||||
color: #46F372; |
||||
margin-left: 2px; |
||||
animation: show-hide-welcome 500ms infinite; |
||||
} |
||||
.inputTextCursor{ |
||||
position: relative; |
||||
margin-left: 2px; |
||||
border-bottom: 4px solid #46F372; |
||||
box-sizing: border-box; |
||||
animation: show-hide-answer 500ms infinite; |
||||
} |
||||
|
||||
@keyframes show-hide-welcome { |
||||
0% { |
||||
color: #46F372; |
||||
background: #46F372; |
||||
} |
||||
50%{ |
||||
color: #46F372; |
||||
background: #46F372; |
||||
} |
||||
100% { |
||||
color: #46F37200; |
||||
background: #46F37200; |
||||
} |
||||
70% { |
||||
color: #46F37200; |
||||
background: #46F37200; |
||||
} |
||||
} |
||||
@keyframes show-hide-answer { |
||||
0% { |
||||
border-bottom: 4px solid #46F372; |
||||
} |
||||
50%{ |
||||
border-bottom: 4px solid #46F372; |
||||
} |
||||
100% { |
||||
border-bottom: 4px solid #46F37200; |
||||
} |
||||
70% { |
||||
border-bottom: 4px solid #46F37200; |
||||
} |
||||
} |
||||
|
||||
.loading{ |
||||
position: absolute; |
||||
bottom: 0px; |
||||
left: 0; |
||||
height: 5px; |
||||
width: 100px; |
||||
background: #46F372; |
||||
animation: loadingSS 1400ms forwards linear; |
||||
z-index: 1000; |
||||
} |
||||
|
||||
@keyframes loadingSS { |
||||
from{ |
||||
width: 0; |
||||
} |
||||
to{ |
||||
width: 100%; |
||||
} |
||||
} |
||||
|
||||
.inputHide{ |
||||
position: absolute; |
||||
height: 0; |
||||
width: 0; |
||||
top:0; |
||||
left: 0; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
|
||||
</body> |
||||
<script> |
||||
window.onload = () => { |
||||
const body = document.body; |
||||
let username, password |
||||
|
||||
// 开局提示 |
||||
const welcomeElement = document.createElement('div'); |
||||
body.append(welcomeElement); |
||||
const welcomeText = `Welcome To <%= title %>.\nYou Are About To Enter The Top Management Interface.`; |
||||
renderText(welcomeText, welcomeElement, () => { |
||||
// 询问是否继续 |
||||
const askElement = document.createElement('div'); |
||||
body.append(askElement); |
||||
const askText = 'Can Verify Your Identity Now? (Yes / No):' |
||||
renderText(askText, askElement, () => { |
||||
// 确认输入 |
||||
const answerElement = document.createElement('div'); |
||||
body.append(answerElement); |
||||
inputText(answerElement, text => { |
||||
const answer = text.toLowerCase() |
||||
if(answer == 'y' || answer == 'yes'){ |
||||
// 用户名提示 |
||||
const userText = 'USERNAME: ' |
||||
const usernameElement = document.createElement('div'); |
||||
const userHeaderElement = document.createElement('span'); |
||||
const userBodyElement = document.createElement('span'); |
||||
body.append(usernameElement) |
||||
usernameElement.append(userHeaderElement) |
||||
usernameElement.append(userBodyElement) |
||||
renderText(userText, userHeaderElement, () => { |
||||
inputText(userBodyElement, usernameText => { |
||||
username = usernameText |
||||
// 密码提示 |
||||
const passwdText = 'PASSWORD: ' |
||||
const passwdElement = document.createElement('div'); |
||||
const passwdHeaderElement = document.createElement('span'); |
||||
const passwdBodyElement = document.createElement('span'); |
||||
body.append(passwdElement) |
||||
passwdElement.append(passwdHeaderElement) |
||||
passwdElement.append(passwdBodyElement) |
||||
renderText(passwdText, passwdHeaderElement, () => { |
||||
inputText(passwdBodyElement, passwdText => { |
||||
password = passwdText |
||||
Entry() |
||||
}, null, true) |
||||
}, 0, 0) |
||||
}, null) |
||||
}, 0, 0) |
||||
}else{ |
||||
setTimeout(() => { |
||||
window.close() |
||||
}, 3000) |
||||
console.log('Window On Close in 3000ms!') |
||||
return; |
||||
} |
||||
}, true) |
||||
}, 0, 0) |
||||
}) |
||||
} |
||||
|
||||
function renderText(text, element, callback, startWait = 2000, endWait= 1000){ |
||||
const textElement = document.createElement('span'); |
||||
const cursorElement = document.createElement('span'); |
||||
cursorElement.classList.add('welecomeTextCursor'); |
||||
cursorElement.innerHTML = ' ' |
||||
|
||||
element.append(textElement, cursorElement) |
||||
const max = text.length |
||||
let nowLetter = 1 |
||||
setTimeout(() => { |
||||
const setTextInterval = setInterval(() => { |
||||
const spanElement = document.createElement('span'); |
||||
const nowText = text[nowLetter - 1] |
||||
if(nowText == ' '){ |
||||
spanElement.innerHTML = ' ' |
||||
}else{ |
||||
spanElement.innerText = nowText |
||||
} |
||||
|
||||
spanElement.classList.add('welecomeText'); |
||||
textElement.append(spanElement) |
||||
if(nowLetter == max){ |
||||
clearInterval(setTextInterval) |
||||
setTimeout(() => { |
||||
cursorElement.remove() |
||||
callback() |
||||
}, endWait) |
||||
}else{ |
||||
nowLetter++ |
||||
} |
||||
}, 30) |
||||
}, startWait) |
||||
|
||||
} |
||||
|
||||
function inputText(element, callback, letter, passwd){ |
||||
const textElement = document.createElement('span'); |
||||
const cursorElement = document.createElement('span'); |
||||
cursorElement.classList.add('inputTextCursor'); |
||||
cursorElement.innerHTML = ' '; |
||||
|
||||
element.append(textElement, cursorElement) |
||||
|
||||
const store = { |
||||
key: '', |
||||
string: '' |
||||
} |
||||
|
||||
document.body.addEventListener('keyup', getKeyWord) |
||||
if(!letter){ |
||||
letter = 'QWERTYUIOPASDFGHJKLZXCVBNM1234567890*@#&' + 'QWERTYUIOPASDFGHJKLZXCVBNM'.toLowerCase().split('') |
||||
}else{ |
||||
letter = ['Y','y','E','e','S','s','N','n','O','o'] |
||||
} |
||||
function getKeyWord(){ |
||||
if(letter && letter.includes(event.key)){ |
||||
store.key = event.key; |
||||
store.string = store.string + event.key |
||||
} |
||||
if(event.key == 'Backspace'){ |
||||
store.string = store.string.slice(0, store.string.length - 1) |
||||
} |
||||
if(event.key == 'Enter'){ |
||||
if(store.string.length==0){ |
||||
|
||||
}else{ |
||||
cursorElement.remove() |
||||
callback(store.string) |
||||
document.body.removeEventListener('keyup', getKeyWord) |
||||
} |
||||
} |
||||
if(passwd){ |
||||
textElement.innerText = new Array(store.string.length).fill('*').join('') |
||||
}else{ |
||||
textElement.innerText = store.string |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
function Entry(){ |
||||
if(true){ |
||||
const entryMessageElement = document.createElement('div') |
||||
document.body.append(entryMessageElement) |
||||
renderText('About To Enter The System!', entryMessageElement, () => { |
||||
const loadingElement = document.createElement('div') |
||||
loadingElement.classList.add('loading') |
||||
document.body.append(loadingElement) |
||||
}, 0, 0) |
||||
|
||||
} |
||||
} |
||||
|
||||
</script> |
||||
</html> |
@ -0,0 +1,21 @@ |
||||
module.exports = { |
||||
root: true, |
||||
env: { browser: true, es2020: true }, |
||||
extends: [ |
||||
'eslint:recommended', |
||||
'plugin:react/recommended', |
||||
'plugin:react/jsx-runtime', |
||||
'plugin:react-hooks/recommended', |
||||
], |
||||
ignorePatterns: ['dist', '.eslintrc.cjs'], |
||||
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, |
||||
settings: { react: { version: '18.2' } }, |
||||
plugins: ['react-refresh'], |
||||
rules: { |
||||
'react-refresh/only-export-components': [ |
||||
'warn', |
||||
{ allowConstantExport: true }, |
||||
], |
||||
indent: [2, 4, { SwitchCase: 1 }], |
||||
}, |
||||
} |
@ -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? |
@ -0,0 +1,8 @@ |
||||
# React + Vite |
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. |
||||
|
||||
Currently, two official plugins are available: |
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh |
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh |
@ -0,0 +1,24 @@ |
||||
<!doctype html> |
||||
<html lang="zh"> |
||||
<head> |
||||
<script> |
||||
window.startime = new Date().getTime() |
||||
</script> |
||||
<meta charset="UTF-8" /> |
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
||||
<link rel="stylesheet" href="/reset.css"> |
||||
<link rel="stylesheet" href="/loading.css"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
<title>Vite + React</title> |
||||
</head> |
||||
<body> |
||||
<div id="root"></div> |
||||
<div id="loading"> |
||||
<div id="loaderBox"> |
||||
<div id="loader"></div> |
||||
<div id="loaderText">waiting</div> |
||||
</div> |
||||
</div> |
||||
<script type="module" src="/src/main.jsx"></script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,30 @@ |
||||
{ |
||||
"name": "react-template", |
||||
"private": true, |
||||
"version": "0.0.0", |
||||
"type": "module", |
||||
"scripts": { |
||||
"dev": "vite", |
||||
"build": "vite build", |
||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", |
||||
"preview": "vite preview" |
||||
}, |
||||
"dependencies": { |
||||
"@reduxjs/toolkit": "^1.9.7", |
||||
"react": "^18.2.0", |
||||
"react-dom": "^18.2.0", |
||||
"react-router-dom": "^6.20.0", |
||||
"redux": "^4.2.1", |
||||
"sass": "^1.69.5" |
||||
}, |
||||
"devDependencies": { |
||||
"@types/react": "^18.2.37", |
||||
"@types/react-dom": "^18.2.15", |
||||
"@vitejs/plugin-react": "^4.2.0", |
||||
"eslint": "^8.53.0", |
||||
"eslint-plugin-react": "^7.33.2", |
||||
"eslint-plugin-react-hooks": "^4.6.0", |
||||
"eslint-plugin-react-refresh": "^0.4.4", |
||||
"vite": "^5.0.0" |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,76 @@ |
||||
body{ |
||||
background: #111111; |
||||
} |
||||
#root{ |
||||
position: relative; |
||||
background: #fefefe; |
||||
height: 100vh; |
||||
width: 100vw; |
||||
} |
||||
.openRoot{ |
||||
animation: openRoot linear 1500ms; |
||||
} |
||||
#loading { |
||||
position: fixed; |
||||
top: 0; |
||||
left: 0; |
||||
width: 100vw; |
||||
height: 100vh; |
||||
min-height: 200px; |
||||
min-width: 200px; |
||||
|
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
|
||||
} |
||||
#loaderBox{ |
||||
opacity: 1; |
||||
transition: opacity linear 500ms; |
||||
} |
||||
.hideLoader{ |
||||
opacity: 0 !important; |
||||
} |
||||
|
||||
|
||||
#loader { |
||||
position: relative; |
||||
width: 60px; |
||||
aspect-ratio: 2; |
||||
--_g: no-repeat radial-gradient(circle closest-side, #aaa 90%, #fff0); |
||||
background: var(--_g) 0% 50%, var(--_g) 50% 50%, var(--_g) 100% 50%; |
||||
background-size: calc(100% / 3) 50%; |
||||
animation: l3 1s infinite linear; |
||||
} |
||||
#loaderText{ |
||||
position: relative; |
||||
text-align: center; |
||||
color: #fefefe; |
||||
line-height: 40px; |
||||
user-select: none; |
||||
color: #666; |
||||
} |
||||
|
||||
|
||||
@keyframes l3 { |
||||
20% { |
||||
background-position: 0% 0%, 50% 50%, 100% 50%; |
||||
} |
||||
40% { |
||||
background-position: 0% 100%, 50% 0%, 100% 50%; |
||||
} |
||||
60% { |
||||
background-position: 0% 50%, 50% 100%, 100% 0%; |
||||
} |
||||
80% { |
||||
background-position: 0% 50%, 50% 50%, 100% 100%; |
||||
} |
||||
} |
||||
@keyframes openRoot { |
||||
from{ |
||||
transform: scale(0.7) |
||||
} |
||||
to{ |
||||
transform: scale(1) |
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
/* http://meyerweb.com/eric/tools/css/reset/ |
||||
v2.0 | 20110126 |
||||
License: none (public domain) |
||||
*/ |
||||
|
||||
html, body, div, span, applet, object, iframe, |
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre, |
||||
a, abbr, acronym, address, big, cite, code, |
||||
del, dfn, em, img, ins, kbd, q, s, samp, |
||||
small, strike, strong, sub, sup, tt, var, |
||||
b, u, i, center, |
||||
dl, dt, dd, ol, ul, li, |
||||
fieldset, form, label, legend, |
||||
table, caption, tbody, tfoot, thead, tr, th, td, |
||||
article, aside, canvas, details, embed, |
||||
figure, figcaption, footer, header, hgroup, |
||||
menu, nav, output, ruby, section, summary, |
||||
time, mark, audio, video { |
||||
margin: 0; |
||||
padding: 0; |
||||
border: 0; |
||||
font-size: 100%; |
||||
font: inherit; |
||||
vertical-align: baseline; |
||||
} |
||||
/* HTML5 display-role reset for older browsers */ |
||||
article, aside, details, figcaption, figure, |
||||
footer, header, hgroup, menu, nav, section { |
||||
display: block; |
||||
} |
||||
body { |
||||
line-height: 1; |
||||
} |
||||
ol, ul { |
||||
list-style: none; |
||||
} |
||||
blockquote, q { |
||||
quotes: none; |
||||
} |
||||
blockquote:before, blockquote:after, |
||||
q:before, q:after { |
||||
content: ''; |
||||
content: none; |
||||
} |
||||
table { |
||||
border-collapse: collapse; |
||||
border-spacing: 0; |
||||
} |
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,13 @@ |
||||
import {useRoutes} from "react-router-dom"; |
||||
import routes from "@routes"; |
||||
|
||||
function App() { |
||||
const elements = useRoutes(routes) |
||||
return ( |
||||
<> |
||||
{elements} |
||||
</> |
||||
) |
||||
} |
||||
|
||||
export default App |
@ -0,0 +1,7 @@ |
||||
import css from './index.module.scss'; |
||||
|
||||
export default function Eye(){ |
||||
return <div className={css.Eye}> |
||||
<div className={css.loader}></div> |
||||
</div> |
||||
} |
@ -0,0 +1,38 @@ |
||||
.Eye{ |
||||
position: relative; |
||||
height: 200px; |
||||
width: 100%; |
||||
background: #333333; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
|
||||
.loader { |
||||
display: inline-flex; |
||||
gap: 10px; |
||||
} |
||||
|
||||
.loader:before, |
||||
.loader:after { |
||||
content: ""; |
||||
height: 20px; |
||||
aspect-ratio: 1; |
||||
border-radius: 50%; |
||||
background: radial-gradient(farthest-side,#000 95%,#0000) 35% 35%/6px 6px no-repeat |
||||
#fff; |
||||
transform: scaleX(var(--s,1)) rotate(0deg); |
||||
animation: l6 1s infinite linear; |
||||
} |
||||
|
||||
.loader:after { |
||||
--s: -1; |
||||
animation-delay: -0.1s; |
||||
} |
||||
|
||||
@keyframes l6 { |
||||
100% { |
||||
transform: scaleX(var(--s,1)) rotate(360deg); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,6 @@ |
||||
import css from './index.module.scss' |
||||
export default function One(){ |
||||
return <div className={css.One}> |
||||
<div className={[css.loader, css["loader_bubble"]].join(' ')}></div> |
||||
</div> |
||||
} |
@ -0,0 +1,51 @@ |
||||
.One{ |
||||
position: relative; |
||||
height: 200px; |
||||
width: 100%; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
|
||||
|
||||
.loader { |
||||
position: relative; |
||||
display: block; |
||||
width: 44px; |
||||
height: 44px; |
||||
font-size: 15px; |
||||
box-shadow: 9px 9px 33px #d1d1d1, -9px -9px 33px #ffffff; |
||||
} |
||||
|
||||
.loader::before, |
||||
.loader::after { |
||||
content: ''; |
||||
position: absolute; |
||||
display: block; |
||||
} |
||||
|
||||
.loader_bubble::before { |
||||
top: 10px; |
||||
left: 10px; |
||||
width: 10px; |
||||
height: 10px; |
||||
background: #fff; |
||||
border-radius: 50%; |
||||
} |
||||
|
||||
.loader_bubble { |
||||
background: linear-gradient(180deg, rgb(0, 91, 228) 10%, rgb(75, 127, 240) 100%); |
||||
border-radius: 50%; |
||||
transform-origin: 50% 50%; |
||||
animation: bubble7234 1200ms cubic-bezier(0.645, 0.045, 0.355, 1) infinite; |
||||
} |
||||
|
||||
@keyframes bubble7234 { |
||||
0% { |
||||
transform: translate3d(0,0,0) rotate(0); |
||||
} |
||||
|
||||
100% { |
||||
transform: translate3d(0,0,0) rotate(360deg); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,15 @@ |
||||
import css from './index.module.scss' |
||||
|
||||
export default function Pacman(){ |
||||
return <div className={css.Pacman}> |
||||
<div className={css["loader-wrapper"]}> |
||||
<div className={css.packman}></div> |
||||
<div className={css.dots}> |
||||
<div className={css.dot}></div> |
||||
<div className={css.dot}></div> |
||||
<div className={css.dot}></div> |
||||
<div className={css.dot}></div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
} |
@ -0,0 +1,123 @@ |
||||
.Pacman{ |
||||
position: relative; |
||||
height: 200px; |
||||
width: 100%; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
background: #333333; |
||||
|
||||
.loader-wrapper { |
||||
position: relative; |
||||
//top: 0; |
||||
//left: 0; |
||||
//bottom: 0; |
||||
//right: 0; |
||||
//margin: auto; |
||||
} |
||||
|
||||
.loader-wrapper .packman::before { |
||||
content: ''; |
||||
position: absolute; |
||||
width: 50px; |
||||
height: 25px; |
||||
background-color: #EFF107; |
||||
border-radius: 100px 100px 0 0; |
||||
transform: translate(-50%, -50%); |
||||
animation: pac-top 0.5s linear infinite; |
||||
transform-origin: center bottom; |
||||
} |
||||
|
||||
.loader-wrapper .packman::after { |
||||
content: ''; |
||||
position: absolute; |
||||
width: 50px; |
||||
height: 25px; |
||||
background-color: #EFF107; |
||||
border-radius: 0 0 100px 100px; |
||||
transform: translate(-50%, 50%); |
||||
animation: pac-bot 0.5s linear infinite; |
||||
transform-origin: center top; |
||||
} |
||||
|
||||
@keyframes pac-top { |
||||
0% { |
||||
transform: translate(-50%, -50%) rotate(0) |
||||
} |
||||
|
||||
50% { |
||||
transform: translate(-50%, -50%) rotate(-30deg) |
||||
} |
||||
|
||||
100% { |
||||
transform: translate(-50%, -50%) rotate(0) |
||||
} |
||||
} |
||||
|
||||
@keyframes pac-bot { |
||||
0% { |
||||
transform: translate(-50%, 50%) rotate(0) |
||||
} |
||||
|
||||
50% { |
||||
transform: translate(-50%, 50%) rotate(30deg) |
||||
} |
||||
|
||||
100% { |
||||
transform: translate(-50%, 50%) rotate(0) |
||||
} |
||||
} |
||||
|
||||
.dot { |
||||
position: absolute; |
||||
z-index: -1; |
||||
top: 8px; |
||||
width: 10px; |
||||
height: 10px; |
||||
border-radius: 50%; |
||||
background: #fff; |
||||
} |
||||
|
||||
.dots .dot:nth-child(1) { |
||||
left: 90px; |
||||
animation: dot-stage1 0.5s infinite; |
||||
} |
||||
|
||||
.dots .dot:nth-child(2) { |
||||
left: 60px; |
||||
animation: dot-stage1 0.5s infinite; |
||||
} |
||||
|
||||
.dots .dot:nth-child(3) { |
||||
left: 30px; |
||||
animation: dot-stage1 0.5s infinite; |
||||
} |
||||
|
||||
.dots .dot:nth-child(4) { |
||||
left: 10px; |
||||
animation: dot-stage2 0.5s infinite; |
||||
} |
||||
|
||||
@keyframes dot-stage1 { |
||||
0% { |
||||
transform: translate(0, 0); |
||||
} |
||||
|
||||
100% { |
||||
transform: translate(-24px, 0); |
||||
} |
||||
} |
||||
|
||||
@keyframes dot-stage2 { |
||||
0% { |
||||
transform: scale(1); |
||||
} |
||||
|
||||
5%, 100% { |
||||
transform: scale(0); |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
} |
@ -0,0 +1,9 @@ |
||||
import css from './index.module.scss' |
||||
|
||||
export default function TableTennis(){ |
||||
return <div className={css.TableTennis}> |
||||
<div className={css.bar}> |
||||
<div className={css.ball}></div> |
||||
</div> |
||||
</div> |
||||
} |
@ -0,0 +1,65 @@ |
||||
.TableTennis{ |
||||
position: relative; |
||||
height: 200px; |
||||
width: 100%; |
||||
background: #333; |
||||
} |
||||
|
||||
|
||||
$up-down6123 : up-down6123; |
||||
$ball-move8234: ball-move8234; |
||||
|
||||
.ball { |
||||
position: relative; |
||||
bottom: 50px; |
||||
left: calc(100% - 20px); |
||||
width: 50px; |
||||
height: 50px; |
||||
background: #fff; |
||||
border-radius: 50%; |
||||
animation: $ball-move8234 3s ease-in-out 1s infinite alternate; |
||||
} |
||||
|
||||
.ball::after { |
||||
position: absolute; |
||||
content: ''; |
||||
top: 25px; |
||||
right: 5px; |
||||
width: 5px; |
||||
height: 5px; |
||||
background: #000; |
||||
border-radius: 50%; |
||||
} |
||||
|
||||
.bar { |
||||
position: relative; |
||||
top:100px; |
||||
width: 200px; |
||||
height: 12.5px; |
||||
background: #FFDAAF; |
||||
border-radius: 30px; |
||||
transform: rotate(-15deg); |
||||
animation: $up-down6123 3s ease-in-out 1s infinite alternate; |
||||
} |
||||
|
||||
@keyframes up-down6123 { |
||||
from { |
||||
transform: rotate(-15deg); |
||||
} |
||||
|
||||
to { |
||||
transform: rotate(15deg); |
||||
} |
||||
} |
||||
|
||||
@keyframes ball-move8234 { |
||||
from { |
||||
left: calc(100% - 40px); |
||||
transform: rotate(360deg); |
||||
} |
||||
|
||||
to { |
||||
left: calc(0% - 20px); |
||||
transform: rotate(0deg); |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
import css from './index.module.scss' |
||||
|
||||
export default function Three(){ |
||||
return <div className={css.Three}> |
||||
<div className={css.loader}></div> |
||||
</div> |
||||
} |
@ -0,0 +1,34 @@ |
||||
.Three{ |
||||
position: relative; |
||||
height: 200px; |
||||
width: 100%; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
background: #111; |
||||
|
||||
|
||||
.loader { |
||||
width: 60px; |
||||
aspect-ratio: 2; |
||||
--_g: no-repeat radial-gradient(circle closest-side, #fff 90%, #fff0); |
||||
background: var(--_g) 0% 50%, var(--_g) 50% 50%, var(--_g) 100% 50%; |
||||
background-size: calc(100% / 3) 50%; |
||||
animation: l3 1s infinite linear; |
||||
} |
||||
@keyframes l3 { |
||||
20% { |
||||
background-position: 0% 0%, 50% 50%, 100% 50%; |
||||
} |
||||
40% { |
||||
background-position: 0% 100%, 50% 0%, 100% 50%; |
||||
} |
||||
60% { |
||||
background-position: 0% 50%, 50% 100%, 100% 0%; |
||||
} |
||||
80% { |
||||
background-position: 0% 50%, 50% 50%, 100% 100%; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,8 @@ |
||||
import css from './index.module.scss' |
||||
|
||||
export default function Two(){ |
||||
return <div className={css.Two}> |
||||
|
||||
<div className={css["chaotic-orbit"]}></div> |
||||
</div> |
||||
} |
@ -0,0 +1,163 @@ |
||||
.Two{ |
||||
position: relative; |
||||
height: 200px; |
||||
width: 100%; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
|
||||
|
||||
|
||||
.chaotic-orbit { |
||||
--uib-size: 25px; |
||||
--uib-speed: 1.5s; |
||||
--uib-color: black; |
||||
position: relative; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
height: var(--uib-size); |
||||
width: var(--uib-size); |
||||
animation: rotate936 calc(var(--uib-speed) * 1.667) infinite linear; |
||||
} |
||||
|
||||
.chaotic-orbit::before, |
||||
.chaotic-orbit::after { |
||||
content: ''; |
||||
position: absolute; |
||||
height: 60%; |
||||
width: 60%; |
||||
border-radius: 50%; |
||||
background-color: var(--uib-color); |
||||
will-change: transform; |
||||
flex-shrink: 0; |
||||
} |
||||
|
||||
.chaotic-orbit::before { |
||||
animation: orbit var(--uib-speed) linear infinite; |
||||
} |
||||
|
||||
.chaotic-orbit::after { |
||||
animation: orbit var(--uib-speed) linear calc(var(--uib-speed) / -2) |
||||
infinite; |
||||
} |
||||
|
||||
@keyframes rotate936 { |
||||
0% { |
||||
transform: rotate(0deg); |
||||
} |
||||
|
||||
100% { |
||||
transform: rotate(360deg); |
||||
} |
||||
} |
||||
|
||||
@keyframes orbit { |
||||
0% { |
||||
transform: translate(calc(var(--uib-size) * 0.5)) scale(0.73684); |
||||
opacity: 0.65; |
||||
} |
||||
|
||||
5% { |
||||
transform: translate(calc(var(--uib-size) * 0.4)) scale(0.684208); |
||||
opacity: 0.58; |
||||
} |
||||
|
||||
10% { |
||||
transform: translate(calc(var(--uib-size) * 0.3)) scale(0.631576); |
||||
opacity: 0.51; |
||||
} |
||||
|
||||
15% { |
||||
transform: translate(calc(var(--uib-size) * 0.2)) scale(0.578944); |
||||
opacity: 0.44; |
||||
} |
||||
|
||||
20% { |
||||
transform: translate(calc(var(--uib-size) * 0.1)) scale(0.526312); |
||||
opacity: 0.37; |
||||
} |
||||
|
||||
25% { |
||||
transform: translate(0%) scale(0.47368); |
||||
opacity: 0.3; |
||||
} |
||||
|
||||
30% { |
||||
transform: translate(calc(var(--uib-size) * -0.1)) scale(0.526312); |
||||
opacity: 0.37; |
||||
} |
||||
|
||||
35% { |
||||
transform: translate(calc(var(--uib-size) * -0.2)) scale(0.578944); |
||||
opacity: 0.44; |
||||
} |
||||
|
||||
40% { |
||||
transform: translate(calc(var(--uib-size) * -0.3)) scale(0.631576); |
||||
opacity: 0.51; |
||||
} |
||||
|
||||
45% { |
||||
transform: translate(calc(var(--uib-size) * -0.4)) scale(0.684208); |
||||
opacity: 0.58; |
||||
} |
||||
|
||||
50% { |
||||
transform: translate(calc(var(--uib-size) * -0.5)) scale(0.73684); |
||||
opacity: 0.65; |
||||
} |
||||
|
||||
55% { |
||||
transform: translate(calc(var(--uib-size) * -0.4)) scale(0.789472); |
||||
opacity: 0.72; |
||||
} |
||||
|
||||
60% { |
||||
transform: translate(calc(var(--uib-size) * -0.3)) scale(0.842104); |
||||
opacity: 0.79; |
||||
} |
||||
|
||||
65% { |
||||
transform: translate(calc(var(--uib-size) * -0.2)) scale(0.894736); |
||||
opacity: 0.86; |
||||
} |
||||
|
||||
70% { |
||||
transform: translate(calc(var(--uib-size) * -0.1)) scale(0.947368); |
||||
opacity: 0.93; |
||||
} |
||||
|
||||
75% { |
||||
transform: translate(0%) scale(1); |
||||
opacity: 1; |
||||
} |
||||
|
||||
80% { |
||||
transform: translate(calc(var(--uib-size) * 0.1)) scale(0.947368); |
||||
opacity: 0.93; |
||||
} |
||||
|
||||
85% { |
||||
transform: translate(calc(var(--uib-size) * 0.2)) scale(0.894736); |
||||
opacity: 0.86; |
||||
} |
||||
|
||||
90% { |
||||
transform: translate(calc(var(--uib-size) * 0.3)) scale(0.842104); |
||||
opacity: 0.79; |
||||
} |
||||
|
||||
95% { |
||||
transform: translate(calc(var(--uib-size) * 0.4)) scale(0.789472); |
||||
opacity: 0.72; |
||||
} |
||||
|
||||
100% { |
||||
transform: translate(calc(var(--uib-size) * 0.5)) scale(0.73684); |
||||
opacity: 0.65; |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,18 @@ |
||||
import React from "react"; |
||||
|
||||
const TableTennis = React.lazy(() => import('@components/HtmlLoading/TableTennis')) |
||||
const Pacman = React.lazy(() => import("@components/HtmlLoading/Pacman/index.jsx")) |
||||
const Eye = React.lazy(() => import("@components/HtmlLoading/Eye/index.jsx")) |
||||
const One = React.lazy(() => import("@components/HtmlLoading/One/index.jsx")) |
||||
const Two = React.lazy(() => import("@components/HtmlLoading/Two/index.jsx")) |
||||
const Three = React.lazy(() => import("@components/HtmlLoading/Three/index.jsx")) |
||||
export default function HtmlLoading(){ |
||||
return <div> |
||||
<TableTennis/> |
||||
<Pacman/> |
||||
<Eye/> |
||||
<One/> |
||||
<Two/> |
||||
<Three/> |
||||
</div> |
||||
} |
@ -0,0 +1,30 @@ |
||||
import React from 'react' |
||||
import ReactDOM from 'react-dom/client' |
||||
import App from './App.jsx' |
||||
import {BrowserRouter} from "react-router-dom"; |
||||
import PrivateRoute from "@routes/PrivateRoute.jsx"; |
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render( |
||||
<React.StrictMode> |
||||
<BrowserRouter> |
||||
<PrivateRoute> |
||||
<App /> |
||||
</PrivateRoute> |
||||
</BrowserRouter> |
||||
</React.StrictMode>, |
||||
) |
||||
|
||||
|
||||
window.onload = function (){ |
||||
// 当所有资源加载完毕 |
||||
// 主加载框 |
||||
const loader = document.querySelector('#loading'); |
||||
// 加载动画 |
||||
const loaderBox = document.querySelector('#loaderBox'); |
||||
// 加载动画开始隐藏 |
||||
loaderBox.classList.add('hideLoader') |
||||
// 到时间移除加载框 |
||||
setTimeout(() => { |
||||
loader.remove() |
||||
}, 500) |
||||
} |
@ -0,0 +1,18 @@ |
||||
import {useEffect} from "react"; |
||||
import { useLocation, useParams, useNavigate } from 'react-router-dom'; |
||||
export default function PrivateRoute(props){ |
||||
const location = useLocation(); |
||||
const params = useParams(); |
||||
const navigate = useNavigate() |
||||
useEffect(() => { |
||||
// console.log(location.pathname); // 获取当前路由路径 |
||||
// console.log(location.search); // 获取查询字符串 |
||||
// console.log(location.hash); // 获取 URL 中的哈希值 |
||||
// console.log(params); // 假设 URL 中有一个名为 "id" 的参数 |
||||
if(location.pathname == '/test'){ |
||||
navigate('/') |
||||
} |
||||
}, [0]); |
||||
|
||||
return props.children |
||||
} |
@ -0,0 +1,48 @@ |
||||
// | ------------------------------------------------------------ |
||||
// | @版本: version 0.1 |
||||
// | @创建人: 【Nie-x7129】 |
||||
// | @E-mail: x71291@outlook.com |
||||
// | @所在项目: react-template |
||||
// | @文件描述: index.jsx - |
||||
// | @创建时间: 2023-11-26 16:05 |
||||
// | @更新时间: 2023-11-26 16:05 |
||||
// | @修改记录: |
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*- |
||||
// | = |
||||
// | ------------------------------------------------------------ |
||||
|
||||
import { Navigate } from 'react-router-dom' |
||||
import React, {lazy} from "react"; |
||||
|
||||
const Test = React.lazy(() => import("@views/Test/index.jsx")) |
||||
const HtmlLoading = React.lazy(() => import("@components/HtmlLoading/index.jsx")) |
||||
|
||||
// 首页 |
||||
const Index = lazy(() => { |
||||
console.log('开始加载') |
||||
console.time(1) |
||||
const components = import('@views/Index/index.jsx') |
||||
console.log('加载完成') |
||||
console.timeEnd(1) |
||||
return components |
||||
}) |
||||
|
||||
|
||||
const routes = [ |
||||
{ |
||||
path: '/about', |
||||
element: <HtmlLoading /> |
||||
}, { |
||||
path: '/home', |
||||
element: <Test /> |
||||
}, /*{ |
||||
path: '/', |
||||
element: <Navigate to='/about' /> |
||||
}*/, { |
||||
path: '/', |
||||
element: <Index/> |
||||
} |
||||
] |
||||
|
||||
export default routes |
||||
|
@ -0,0 +1,23 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: react-template
|
||||
// | @文件描述: index.js -
|
||||
// | @创建时间: 2023-11-26 16:47
|
||||
// | @更新时间: 2023-11-26 16:47
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
import { configureStore } from '@reduxjs/toolkit'; |
||||
import DefaultSlice from "@/store/slice/defaultSlice.js"; |
||||
|
||||
const store = configureStore({ |
||||
reducer: { |
||||
defaultSlice: DefaultSlice, |
||||
}, |
||||
}); |
||||
|
||||
export default store; |
@ -0,0 +1,63 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: react-template
|
||||
// | @文件描述: defaultSlice.js -
|
||||
// | @创建时间: 2023-11-26 17:20
|
||||
// | @更新时间: 2023-11-26 17:20
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
|
||||
import {createAsyncThunk, createSlice} from '@reduxjs/toolkit'; |
||||
|
||||
export const decremented = createAsyncThunk( |
||||
'user/fetchData', |
||||
async (data, thunkAPI) => { |
||||
// 调用API
|
||||
console.log(data, thunkAPI) |
||||
await new Promise(res => { |
||||
|
||||
setTimeout(() =>{ |
||||
res() |
||||
}, 1000) |
||||
}) |
||||
return; // 或根据你的API返回结构进行调整
|
||||
} |
||||
); |
||||
|
||||
|
||||
export const defaultSlice = createSlice({ |
||||
name: 'defaultSlice', |
||||
initialState: { |
||||
value: 0, |
||||
}, |
||||
reducers: { |
||||
incremented: (state,b) => { |
||||
state.value += 1; |
||||
}, |
||||
}, |
||||
extraReducers: { |
||||
// 当 fetchUserData action 被派发时,根据 action 的不同状态对 state 进行更新
|
||||
[decremented.pending]: (state, action) => { |
||||
// state.isLoading = true;
|
||||
// state.error = null;
|
||||
}, |
||||
[decremented.fulfilled]: (state, action) => { |
||||
// state.isLoading = false;
|
||||
// state.data = action.payload; // 假设我们的API返回了用户的数据结构
|
||||
state.value -= 1; |
||||
}, |
||||
[decremented.rejected]: (state, action) => { |
||||
// state.isLoading = false;
|
||||
// state.error = action.error.message;
|
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
export const { incremented } = defaultSlice.actions; |
||||
|
||||
export default defaultSlice.reducer; |
@ -0,0 +1,14 @@ |
||||
// 本页样式 |
||||
import css from './index.module.scss' |
||||
// 主图 |
||||
import indexPhoto from './index.png' |
||||
export default function Index(){ |
||||
return <div className={css.Index}> |
||||
<div className={css.container}> |
||||
<div className={css.left}> |
||||
{/*<img src={indexPhoto} alt=""/>*/} |
||||
</div> |
||||
<div className={css.right}></div> |
||||
</div> |
||||
</div> |
||||
} |
@ -0,0 +1,57 @@ |
||||
.Index{ |
||||
position: relative; |
||||
height: 100%; |
||||
width: 100%; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
background-image: url("index.png"); |
||||
|
||||
background-repeat: no-repeat; |
||||
background-position: center; |
||||
background-size: cover; |
||||
&:before{ |
||||
content: ' '; |
||||
position: absolute; |
||||
left: 0; |
||||
width: 0; |
||||
height: 100%; |
||||
width: 100%; |
||||
backdrop-filter: |
||||
blur(100px) // 模糊 |
||||
brightness(0.8) // 亮度 |
||||
contrast(1) // 对比度 |
||||
//grayscale(0.5) // 灰度 |
||||
//hue-rotate(90deg) // 色调 |
||||
saturate(5) |
||||
//opacity(1) |
||||
; |
||||
//backdrop-filter: ; |
||||
} |
||||
& > div.container{ |
||||
position: relative; |
||||
border-radius: 5px; |
||||
box-shadow: 1px 1px 10px 0px #33333366; |
||||
background: #fefefe11; |
||||
padding: 10px; |
||||
box-sizing: border-box; |
||||
height: 320px; |
||||
aspect-ratio: 1/0.618; |
||||
display: flex; |
||||
& > div{ |
||||
position: relative; |
||||
flex: 1; |
||||
} |
||||
& > div.left{ |
||||
background-image: url("index.png"); |
||||
background-repeat: no-repeat; |
||||
background-position: center; |
||||
background-size: cover; |
||||
border-radius: 5px 0 0 5px; |
||||
} |
||||
& > div.right{ |
||||
|
||||
border-radius: 0 5px 5px 0; |
||||
} |
||||
} |
||||
} |
After Width: | Height: | Size: 2.1 MiB |
@ -0,0 +1,24 @@ |
||||
import {useEffect, useState} from "react"; |
||||
import store from "@/store/index.js"; |
||||
import {incremented, decremented} from "@/store/slice/defaultSlice.js"; |
||||
|
||||
export default function Test(){ |
||||
|
||||
const [count, setCount] = useState(store.getState().defaultSlice.value); |
||||
|
||||
useEffect(() => { |
||||
console.log(store.getState()) |
||||
const unsubscribe = store.subscribe(() => { |
||||
setCount(store.getState().defaultSlice.value); |
||||
}); |
||||
return unsubscribe; // 在组件卸载时取消订阅 |
||||
}, []); |
||||
|
||||
|
||||
|
||||
return <div> |
||||
<h1>{count}</h1> |
||||
<button onClick={() => store.dispatch(incremented())}>Increment</button> |
||||
<button onClick={() => store.dispatch(decremented(1))}>Decrement</button> |
||||
</div> |
||||
} |
@ -0,0 +1 @@ |
||||
https://blog.csdn.net/qq_30769437/article/details/128149273 |
@ -0,0 +1,314 @@ |
||||
如果你需要创建一个Redux应用,但不想使用react-redux和`createStore`(这通常是Redux的标准API用于创建store的方法),你可以使用Redux Toolkit中的`configureStore`作为`createStore`的替代方案来创建store。 |
||||
|
||||
Redux Toolkit 是官方推荐的方式来设置store,它提供了更简化的API和一些有用的工具,比如默认集成了redux-thunk中间件以及Redux DevTools扩展支持。下面是一个不使用react-redux的示例,但是如果你想完全不使用任何辅助工具或库,仅使用最基本的Redux API(这是不推荐的,因为你会失去许多便利和性能优化),那你将需要自己管理store的状态订阅和更新组件的渲染。 |
||||
|
||||
下面是一个不使用`createStore`和`react-redux`的Redux应用例子: |
||||
|
||||
目录结构: |
||||
``` |
||||
my-app/ |
||||
src/ |
||||
store/ |
||||
index.js // 创建和配置应用程序的Redux store |
||||
counterReducer.js // 管理计数状态的reducer |
||||
components/ |
||||
Counter.js // 一个使用Redux store的React组件 |
||||
App.js // 应用程序的根组件 |
||||
index.js // 应用程序的主入口文件 |
||||
``` |
||||
|
||||
`store/index.js`: |
||||
```javascript |
||||
import { configureStore } from '@reduxjs/toolkit'; |
||||
import counterReducer from './counterReducer'; |
||||
|
||||
const store = configureStore({ |
||||
reducer: { |
||||
counter: counterReducer, |
||||
}, |
||||
}); |
||||
|
||||
export default store; |
||||
``` |
||||
|
||||
`store/counterReducer.js`: |
||||
```javascript |
||||
const INCREMENT = 'INCREMENT'; |
||||
const DECREMENT = 'DECREMENT'; |
||||
|
||||
const initialState = { |
||||
value: 0, |
||||
}; |
||||
|
||||
function counterReducer(state = initialState, action) { |
||||
switch (action.type) { |
||||
case INCREMENT: |
||||
return { ...state, value: state.value + 1 }; |
||||
case DECREMENT: |
||||
return { ...state, value: state.value - 1 }; |
||||
default: |
||||
return state; |
||||
} |
||||
} |
||||
|
||||
export const increment = () => ({ type: INCREMENT }); |
||||
export const decrement = () => ({ type: DECREMENT }); |
||||
|
||||
export default counterReducer; |
||||
``` |
||||
|
||||
`components/Counter.js`: |
||||
```jsx |
||||
import React, { useState, useEffect } from 'react'; |
||||
import store from '../store'; |
||||
import { increment, decrement } from '../store/counterReducer'; |
||||
|
||||
export const Counter = () => { |
||||
const [count, setCount] = useState(store.getState().counter.value); |
||||
|
||||
useEffect(() => { |
||||
const unsubscribe = store.subscribe(() => { |
||||
setCount(store.getState().counter.value); |
||||
}); |
||||
return unsubscribe; // 在组件卸载时取消订阅 |
||||
}, []); |
||||
|
||||
return ( |
||||
<div> |
||||
<h1>{count}</h1> |
||||
<button onClick={() => store.dispatch(increment())}>Increment</button> |
||||
<button onClick={() => store.dispatch(decrement())}>Decrement</button> |
||||
</div> |
||||
); |
||||
}; |
||||
``` |
||||
|
||||
`App.js`: |
||||
```jsx |
||||
import React from 'react'; |
||||
import { Counter } from './components/Counter'; |
||||
|
||||
const App = () => { |
||||
return ( |
||||
<div> |
||||
<Counter /> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
export default App; |
||||
``` |
||||
|
||||
`index.js`: |
||||
```jsx |
||||
import React from 'react'; |
||||
import ReactDOM from 'react-dom'; |
||||
import App from './App'; |
||||
import store from './store'; |
||||
import { increment } from './store/counterReducer'; |
||||
|
||||
// 可以在这里测试我们的Redux store是否正常工作 |
||||
console.log(store.getState()); // 应该看到初始状态 |
||||
store.dispatch(increment()); |
||||
console.log(store.getState()); // 状态应该更新,并增加了1 |
||||
|
||||
ReactDOM.render( |
||||
<React.StrictMode> |
||||
<App /> |
||||
</React.StrictMode>, |
||||
document.getElementById('root') |
||||
); |
||||
``` |
||||
|
||||
在上述的示例中,我们使用`useEffect`来订阅Redux store和清理订阅,通过store的`getState`方法获取当前的状态,并通过`dispatch`方法来派发actions。这种方式下,我们没有使用react-redux,而是手动连接到Redux store并监听状态变化。 |
||||
|
||||
请注意,这样手动处理Redux可能会使你的应用程序难以管理和扩展,特别是在大型应用程序中,react-redux提供的`Provider`和`connect`方法或者hooks`useSelector`和`useDispatch`可以更好地帮助你管理连接逻辑和组件更新,提高应用的性能。 |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
在 Redux Toolkit 中,`createSlice` 通常用于同步更新 state ,但当它与 `createAsyncThunk` 一起使用时,可以非常容易地用于异步更新。`createAsyncThunk` 会生成三种 action 类型(pending,fulfilled,rejected),你可以在 `createSlice` 的 `extraReducers` 属性中监听这些 action: |
||||
|
||||
```javascript |
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; |
||||
import { userAPI } from './userAPI'; |
||||
|
||||
// 首先创建一个异步 thunk action |
||||
export const fetchUserData = createAsyncThunk( |
||||
'user/fetchData', |
||||
async (userId, thunkAPI) => { |
||||
// 调用API |
||||
const response = await userAPI.fetchById(userId); |
||||
return response.data; // 或根据你的API返回结构进行调整 |
||||
} |
||||
); |
||||
|
||||
// 然后创建 slice,这里我们可以处理我们上面定义的三种状态的actions |
||||
const userSlice = createSlice({ |
||||
name: 'user', |
||||
initialState: { |
||||
data: null, |
||||
isLoading: false, |
||||
error: null, |
||||
}, |
||||
reducers: { |
||||
// 你的其他同步 reducers 可以在这里定义 |
||||
}, |
||||
extraReducers: { |
||||
// 当 fetchUserData action 被派发时,根据 action 的不同状态对 state 进行更新 |
||||
[fetchUserData.pending]: (state, action) => { |
||||
state.isLoading = true; |
||||
state.error = null; |
||||
}, |
||||
[fetchUserData.fulfilled]: (state, action) => { |
||||
state.isLoading = false; |
||||
state.data = action.payload; // 假设我们的API返回了用户的数据结构 |
||||
}, |
||||
[fetchUserData.rejected]: (state, action) => { |
||||
state.isLoading = false; |
||||
state.error = action.error.message; |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
export const { actions, reducer } = userSlice; |
||||
``` |
||||
|
||||
在这个例子中: |
||||
|
||||
- `fetchUserData.pending` 相关的 reducer 将在请求开始时将 `isLoading` 状态设置为 true。 |
||||
- `fetchUserData.fulfilled` 相关的 reducer 将在请求成功并收到数据时更新 `data` ,并将 `isLoading` 设置为 false。 |
||||
- `fetchUserData.rejected` 相关的 reducer 将在请求失败时设置错误信息,并将 `isLoading` 设置为 false。 |
||||
|
||||
通过使用 `createAsyncThunk` 和 `createSlice` 的 `extraReducers` ,我们也很方便的可以将异步行为整合到我们的 Redux state 管理中。 |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
在视图(常常指的是 React 组件)中使用 `@reduxjs/toolkit` 中 `createAsyncThunk` 创建的异步 action 通常涉及以下几个步骤: |
||||
|
||||
1. 将 Redux store 和 React 组件连接起来,可以使用 `react-redux` 提供的 `useSelector` 来选择 state,以及 `useDispatch` 来派发 actions。 |
||||
|
||||
2. 创建视图(UI组件),并在适当的时候(如组件挂载或者用户互动事件)派发异步 action。 |
||||
|
||||
3. 显示加载状态、错误信息或者成功得到的数据。 |
||||
|
||||
下面是一个例子,展示了如何在一个 React 组件中使用异步 action: |
||||
|
||||
```jsx |
||||
import React, { useEffect } from 'react'; |
||||
import { useSelector, useDispatch } from 'react-redux'; |
||||
import { fetchUserData } from './userSlice'; |
||||
// 以上的 userSlice 是我们之前定义的包含 fetchUserData 异步 action 的文件 |
||||
|
||||
function UserComponent({ userId }) { |
||||
const dispatch = useDispatch(); |
||||
|
||||
// 从 Redux state 中选择数据 |
||||
const user = useSelector(state => state.user.data); |
||||
const isLoading = useSelector(state => state.user.isLoading); |
||||
const error = useSelector(state => state.user.error); |
||||
|
||||
// 当组件挂载时,派发 fetchUserData action |
||||
useEffect(() => { |
||||
dispatch(fetchUserData(userId)); |
||||
}, [userId, dispatch]); |
||||
|
||||
// 根据 state 渲染 UI |
||||
if (isLoading) { |
||||
return <div>Loading...</div>; |
||||
} |
||||
|
||||
if (error) { |
||||
return <div>Error: {error}</div>; |
||||
} |
||||
|
||||
return ( |
||||
<div> |
||||
<h1>User Data</h1> |
||||
{/* 假设 user 对象有 name 属性 */} |
||||
{user ? <p>Name: {user.name}</p> : <p>No user details</p>} |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
export default UserComponent; |
||||
``` |
||||
|
||||
在这个 React 组件中: |
||||
|
||||
- 使用 `useDispatch` hook 获取 `dispatch` 方法,以便于我们可以派发 actions。 |
||||
- 使用 `useSelector` hook 从 Redux store 中选择 `user.state` 中的各部分数据。 |
||||
- 使用 `useEffect` React hook 来触发 `fetchUserData` 异步 action。当 `userId` 改变或者组件首次渲染时会重新触发。 |
||||
- 在 UI 中根据 `isLoading`, `error`, 和 `user` 状态显示不同的内容。如果 `isLoading` 为 true,显示加载状态;如果 `error` 存在,则显示错误;否则显示用户信息。 |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
在 `createAsyncThunk` 中使用的 `thunkAPI` 对象是由 Redux Thunk 提供的一个参数,包含了几个有用的属性和方法,使得在 thunk 中可以执行更为复杂的逻辑。`thunkAPI` 在每次调用异步 thunk 函数时都会被提供,并且具有以下字段和方法: |
||||
|
||||
1. **dispatch**: 允许你在 thunk 中派发 action。 |
||||
2. **getState**: 允许你访问当前的 Redux store state。 |
||||
3. **extra**: 如果在创建 Redux store 的 `configureStore` 方法中定义了 `extraArgument`,则这里可以取得。 |
||||
4. **requestId**: 是对每次异步 thunk action 调用的唯一标识。 |
||||
5. **signal**: 是一个 `AbortSignal` 对象,与本次异步操作相关联,可以用来响应取消操作。 |
||||
6. **rejectWithValue**: 一个函数,允许你在发生错误时手动地派发一个拒绝 (rejected) action,并携带自定义的 payload 值。 |
||||
7. **fulfillWithValue**: 当你需要在 resolve (解决) action 中提供一个不同于异步操作返回结果的 payload 时,可以用这个函数。 |
||||
8. **rejectWithReason**: 类似 `rejectWithValue`,允许在 rejected action 中提供自定义拒绝原因,更明确地说明拒绝的原因。 |
||||
|
||||
这些功能给予开发者很大的灵活性去处理异步逻辑和实现复杂的异步操作流程。例如,使用 `dispatch` 来派发其他 actions,利用 `getState` 获取最新的 state 来指导后续逻辑,或使用 `rejectWithValue` 在出错时捕捉错误并优化错误处理。 |
||||
|
||||
以下是一个使用 `thunkAPI` 的例子: |
||||
|
||||
```javascript |
||||
export const fetchUserData = createAsyncThunk( |
||||
'user/fetchData', |
||||
async (userId, thunkAPI) => { |
||||
try { |
||||
const response = await userAPI.fetchById(userId); |
||||
return response.data; |
||||
} catch (error) { |
||||
// 如果 API 抛出一个错误,我们可以选择发送一个拒绝action,并附带一个自定义的payload |
||||
return thunkAPI.rejectWithValue({errorMessage: 'Cannot load user data'}); |
||||
} |
||||
} |
||||
); |
||||
``` |
||||
|
||||
在这个例子中,如果 `userAPI.fetchById` 方法抛出一个错误,`thunkAPI.rejectWithValue` 方法则被用来派发一个拒绝的 action,并附上一个含错误信息的 payload。这使得 reducer 可以捕捉这个拒绝的 action,并根据附带的 payload 更新 state。 |
@ -0,0 +1,17 @@ |
||||
import { defineConfig } from 'vite' |
||||
import react from '@vitejs/plugin-react' |
||||
import path from 'path' |
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({ |
||||
plugins: [react()], |
||||
resolve: { |
||||
alias: { |
||||
'@': path.resolve(__dirname, './src'), |
||||
'@views':path.resolve(__dirname, './src/views'), |
||||
'@components': path.resolve(__dirname, './src/components'), |
||||
'@utils': path.resolve(__dirname, './src/utils'), |
||||
'@routes': path.resolve(__dirname, './src/routes') |
||||
}, |
||||
}, |
||||
}) |
@ -0,0 +1,65 @@ |
||||
module.exports = { |
||||
"env": { |
||||
"browser": true, |
||||
"es2021": true, |
||||
"es2022": true, |
||||
"es2023": true, |
||||
}, |
||||
"extends": ["eslint:recommended"], |
||||
"overrides": [ |
||||
{ |
||||
"env": { |
||||
"node": true |
||||
}, |
||||
"files": [ |
||||
".eslintrc.{js,cjs}" |
||||
], |
||||
"parserOptions": { |
||||
"sourceType": "script" |
||||
} |
||||
} |
||||
], |
||||
"parserOptions": { |
||||
"ecmaVersion": "latest", |
||||
"sourceType": "module" |
||||
}, |
||||
"rules": { |
||||
indent: ['error', 4, { "SwitchCase": 1 }], // 用于指定代码缩进的方式,这里配置为使用四个空格进行缩进。 |
||||
// 'linebreak-style': [0, 'error', 'windows'], // 用于指定换行符的风格,这里配置为使用 Windows 风格的换行符(\r\n)。 |
||||
quotes: ['error', 'single'], // 用于指定字符串的引号风格,这里配置为使用单引号作为字符串的引号。 |
||||
semi: ['error', 'always'], //用于指定是否需要在语句末尾添加分号,这里配置为必须始终添加分号。 |
||||
"no-console": 2,//禁止使用console |
||||
"no-const-assign": 2,//禁止修改const声明的变量 |
||||
"no-empty": 2,//块语句中的内容不能为空 |
||||
"no-extra-parens": 2,//禁止非必要的括号 |
||||
"no-extra-semi": 2,//禁止多余的冒号 |
||||
"no-fallthrough": 1,//禁止switch穿透 |
||||
"no-func-assign": 2,//禁止重复的函数声明 |
||||
"no-inline-comments": 2,//禁止行内备注 |
||||
"no-irregular-whitespace": 2,//不能有不规则的空格 |
||||
"no-mixed-spaces-and-tabs": [2, false],//禁止混用tab和空格 |
||||
"no-multi-spaces": 1,//不能用多余的空格 |
||||
"no-multiple-empty-lines": [1, {"max": 2}],//空行最多不能超过2行 |
||||
"no-nested-ternary": 0,//禁止使用嵌套的三目运算 |
||||
"no-redeclare": 2,//禁止重复声明变量 |
||||
"no-shadow": 2,//外部作用域中的变量不能与它所包含的作用域中的变量或参数同名 |
||||
"no-trailing-spaces": 2,//一行结束后面不要有空格 |
||||
"no-unexpected-multiline": 2,//避免多行表达式 |
||||
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],//不能有声明后未被使用的变量或参数 |
||||
"no-use-before-define": 2,//未定义前不能使用 |
||||
"no-var": 2,//禁用var,用let和const代替 |
||||
"arrow-parens": 0,//箭头函数用小括号括起来 |
||||
"array-bracket-spacing": [2, "never"],//是否允许非空数组里面有多余的空格 |
||||
"camelcase": 2,//强制驼峰法命名 |
||||
"comma-style": [2, "last"],//逗号风格,换行时在行首还是行尾 |
||||
"comma-spacing": ["error", {"before": false, "after": true}],//对象字面量中冒号的前后空格 |
||||
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],// 冒号后面有空格 |
||||
"lines-around-comment": 0,//行前/行后备注 |
||||
"array-bracket-spacing": ["error", "always"],// 检查数组字面量中的元素之间的空格。 |
||||
}, |
||||
"globals": { |
||||
global: true, |
||||
Buffer: true, |
||||
process: true |
||||
} |
||||
} |
@ -0,0 +1,135 @@ |
||||
# ---> Node |
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
lerna-debug.log* |
||||
pnpm-debug.log* |
||||
.pnpm-debug.log* |
||||
# log |
||||
winston-logs/* |
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html) |
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
||||
|
||||
# Runtime data |
||||
pids |
||||
*.pid |
||||
*.seed |
||||
*.pid.lock |
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover |
||||
lib-cov |
||||
|
||||
# Coverage directory used by tools like istanbul |
||||
coverage |
||||
*.lcov |
||||
|
||||
# nyc test coverage |
||||
.nyc_output |
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) |
||||
.grunt |
||||
|
||||
# Bower dependency directory (https://bower.io/) |
||||
bower_components |
||||
|
||||
# node-waf configuration |
||||
.lock-wscript |
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html) |
||||
build/Release |
||||
|
||||
# Dependency directories |
||||
node_modules/ |
||||
jspm_packages/ |
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/) |
||||
web_modules/ |
||||
|
||||
# TypeScript cache |
||||
*.tsbuildinfo |
||||
|
||||
# Optional npm cache directory |
||||
.npm |
||||
|
||||
# Optional eslint cache |
||||
.eslintcache |
||||
|
||||
# Optional stylelint cache |
||||
.stylelintcache |
||||
|
||||
# Microbundle cache |
||||
.rpt2_cache/ |
||||
.rts2_cache_cjs/ |
||||
.rts2_cache_es/ |
||||
.rts2_cache_umd/ |
||||
|
||||
# Optional REPL history |
||||
.node_repl_history |
||||
|
||||
# Output of 'npm pack' |
||||
*.tgz |
||||
|
||||
# Yarn Integrity file |
||||
.yarn-integrity |
||||
|
||||
# dotenv environment variable files |
||||
.env |
||||
.env.development.local |
||||
.env.test.local |
||||
.env.production.local |
||||
.env.local |
||||
|
||||
# parcel-bundler cache (https://parceljs.org/) |
||||
.cache |
||||
.parcel-cache |
||||
|
||||
# Next.js build output |
||||
.next |
||||
out |
||||
|
||||
# Nuxt.js build / generate output |
||||
.nuxt |
||||
dist |
||||
|
||||
# Gatsby files |
||||
.cache/ |
||||
# Comment in the public line in if your project uses Gatsby and not Next.js |
||||
# https://nextjs.org/blog/next-9-1#public-directory-support |
||||
# public |
||||
|
||||
# vuepress build output |
||||
.vuepress/dist |
||||
|
||||
# vuepress v2.x temp and cache directory |
||||
.temp |
||||
.cache |
||||
|
||||
# Docusaurus cache and generated files |
||||
.docusaurus |
||||
|
||||
# Serverless directories |
||||
.serverless/ |
||||
|
||||
# FuseBox cache |
||||
.fusebox/ |
||||
|
||||
# DynamoDB Local files |
||||
.dynamodb/ |
||||
|
||||
# TernJS port file |
||||
.tern-port |
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions |
||||
.vscode-test |
||||
|
||||
# yarn v2 |
||||
.yarn/cache |
||||
.yarn/unplugged |
||||
.yarn/build-state.yml |
||||
.yarn/install-state.gz |
||||
.pnp.* |
||||
|
@ -0,0 +1,5 @@ |
||||
{ |
||||
"singleQuote": true, |
||||
"trailingComma": "all", |
||||
"tabWidth": 4 |
||||
} |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,110 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: initkoa
|
||||
// | @文件描述: bootstrap.js - Koa项目启动文件
|
||||
// | @创建时间: 2023-11-25 21:17
|
||||
// | @更新时间: 2023-11-25 21:17
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
|
||||
import devConfig from '#root/development.env.js'; |
||||
import prodConfig from '#root/production.env.js'; |
||||
import startApp from '#home/app.js'; |
||||
import winston from "winston"; |
||||
import {logger, colorizer} from "#common/logger/index.js"; |
||||
import createDatabase from "#common/database/index.js"; |
||||
import {createCatch} from "#cache/index.js"; |
||||
import initData from "#common/database/initData.js"; |
||||
// | 获取ENV
|
||||
const ENV = process.env.NODE_ENV && process.env.NODE_ENV.toLowerCase().trim(); |
||||
|
||||
if (ENV === 'development' || ENV === undefined) { |
||||
global.config = devConfig; |
||||
logger.add(new winston.transports.Console({ |
||||
format:winston.format.combine( |
||||
winston.format.printf( |
||||
(i) => { |
||||
return colorizer.colorize( |
||||
i.level, |
||||
i.message |
||||
); |
||||
}, |
||||
), |
||||
) |
||||
})) |
||||
} else if (ENV === 'production') { |
||||
global.config = prodConfig; |
||||
logger.exceptions.handle(new winston.transports.File({ filename: 'winston-logs/winston-exceptions.log' })); |
||||
logger.rejections.handle(new winston.transports.File({ filename: 'winston-logs/winston-rejections.log' })) |
||||
} else { |
||||
throw new Error(`未识别的环境变量${ENV}`); |
||||
} |
||||
global.logger = logger |
||||
global.ENV = ENV; |
||||
|
||||
// = 函数名: checkPort
|
||||
// = 描述: 检测服务端口是否正常
|
||||
// = 参数: None
|
||||
// = 返回值: undefined
|
||||
// = 创建人: nie
|
||||
// = 创建时间: 2023-11-25 21:49:22 -
|
||||
function checkPort() { |
||||
if ( |
||||
typeof global.config.port !== 'number' || |
||||
global.config.port > 65536 || |
||||
global.config.port < 3000 |
||||
) { |
||||
throw new Error(`服务监听端口不合法:${global.config.port}`); |
||||
} |
||||
} |
||||
checkPort(); |
||||
|
||||
// = 函数名: checkAppName
|
||||
// = 描述: 检测服务名是否正常
|
||||
// = 参数: None
|
||||
// = 返回值: undefined
|
||||
// = 创建人: nie
|
||||
// = 创建时间: 2023-11-25 21:51:03 -
|
||||
function checkAppName() { |
||||
if (!global.config.appName) { |
||||
throw new Error(`服务名不存在:${global.config.appName}`); |
||||
} |
||||
} |
||||
checkAppName() |
||||
// process.stdout.write('\u001b[2J\u001b[0;0H');
|
||||
async function createApp(){ |
||||
const sequelize = createDatabase(logger); |
||||
|
||||
await sequelize.sync({alter: true}) |
||||
await sequelize.authenticate().catch(e => { |
||||
console.error(`数据库连接失败, ${e}`); |
||||
throw new Error(e) |
||||
}); |
||||
logger.info(`== 已成功与数据库建立连接。 ==`); |
||||
|
||||
await initData(sequelize) |
||||
|
||||
await createCatch(sequelize) |
||||
|
||||
|
||||
|
||||
|
||||
const app = startApp(); |
||||
|
||||
sequelize.getQueryInterface().showAllTables().then(data => { |
||||
// console.log(data)
|
||||
}).catch(e => { |
||||
console.error(e) |
||||
}) |
||||
|
||||
app.context.sequelize = sequelize |
||||
app.listen(config.port); |
||||
logger.info( `Web服务 ${global.config.appName} 启动成功,访问: http://127.0.0.1:${global.config.port}`) |
||||
} |
||||
createApp() |
||||
|
@ -0,0 +1,21 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: initkoa
|
||||
// | @文件描述: development.env.js -
|
||||
// | @创建时间: 2023-11-25 21:36
|
||||
// | @更新时间: 2023-11-25 21:36
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
import prodConfig from "#root/production.env.js"; |
||||
|
||||
const config = { |
||||
} |
||||
|
||||
const devConfig = {...prodConfig, ...config} |
||||
|
||||
export default devConfig |
@ -0,0 +1,38 @@ |
||||
var DataTypes = require("sequelize").DataTypes; |
||||
var _lauchuser = require("./lauchuser"); |
||||
var _lauchuserexpandfield = require("./lauchuserexpandfield"); |
||||
var _lauchuserexpandrecord = require("./lauchuserexpandrecord"); |
||||
var _lauchuserloginrecord = require("./lauchuserloginrecord"); |
||||
var _lauchuserpasswd = require("./lauchuserpasswd"); |
||||
var _lauchuserstructauthority = require("./lauchuserstructauthority"); |
||||
var _lauchuserstructorganization = require("./lauchuserstructorganization"); |
||||
var _lauchuserstructrelationorganizationaccent = require("./lauchuserstructrelationorganizationaccent"); |
||||
var _lauchuserstructrelationorganizationauthority = require("./lauchuserstructrelationorganizationauthority"); |
||||
|
||||
function initModels(sequelize) { |
||||
var lauchuser = _lauchuser(sequelize, DataTypes); |
||||
var lauchuserexpandfield = _lauchuserexpandfield(sequelize, DataTypes); |
||||
var lauchuserexpandrecord = _lauchuserexpandrecord(sequelize, DataTypes); |
||||
var lauchuserloginrecord = _lauchuserloginrecord(sequelize, DataTypes); |
||||
var lauchuserpasswd = _lauchuserpasswd(sequelize, DataTypes); |
||||
var lauchuserstructauthority = _lauchuserstructauthority(sequelize, DataTypes); |
||||
var lauchuserstructorganization = _lauchuserstructorganization(sequelize, DataTypes); |
||||
var lauchuserstructrelationorganizationaccent = _lauchuserstructrelationorganizationaccent(sequelize, DataTypes); |
||||
var lauchuserstructrelationorganizationauthority = _lauchuserstructrelationorganizationauthority(sequelize, DataTypes); |
||||
|
||||
|
||||
return { |
||||
lauchuser, |
||||
lauchuserexpandfield, |
||||
lauchuserexpandrecord, |
||||
lauchuserloginrecord, |
||||
lauchuserpasswd, |
||||
lauchuserstructauthority, |
||||
lauchuserstructorganization, |
||||
lauchuserstructrelationorganizationaccent, |
||||
lauchuserstructrelationorganizationauthority, |
||||
}; |
||||
} |
||||
module.exports = initModels; |
||||
module.exports.initModels = initModels; |
||||
module.exports.default = initModels; |
@ -0,0 +1,58 @@ |
||||
const Sequelize = require('sequelize'); |
||||
module.exports = function (sequelize, DataTypes) { |
||||
return sequelize.define('lauchuser', { |
||||
uuid: { |
||||
type: DataTypes.UUID, |
||||
allowNull: false, |
||||
primaryKey: true, |
||||
comment: "用户唯一ID" |
||||
}, |
||||
username: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false, |
||||
comment: "用户名" |
||||
}, |
||||
email: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false |
||||
}, |
||||
createTime: { |
||||
type: DataTypes.DATE, |
||||
allowNull: false, |
||||
comment: "创建时间" |
||||
}, |
||||
status: { |
||||
type: DataTypes.INTEGER.UNSIGNED.ZEROFILL, |
||||
allowNull: false, |
||||
defaultValue: 0000000000, |
||||
comment: "0正常1注销2停用" |
||||
}, |
||||
selfSequence: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
autoIncrement: true, |
||||
comment: "自增序列" |
||||
} |
||||
}, { |
||||
sequelize, |
||||
tableName: 'lauchuser', |
||||
timestamps: false, |
||||
indexes: [ |
||||
{ |
||||
name: "PRIMARY", |
||||
unique: true, |
||||
using: "BTREE", |
||||
fields: [ |
||||
{name: "uuid"}, |
||||
] |
||||
}, |
||||
{ |
||||
name: "selfSequence", |
||||
using: "BTREE", |
||||
fields: [ |
||||
{name: "selfSequence"}, |
||||
] |
||||
}, |
||||
] |
||||
}); |
||||
}; |
@ -0,0 +1,82 @@ |
||||
const Sequelize = require('sequelize'); |
||||
module.exports = function(sequelize, DataTypes) { |
||||
return sequelize.define('lauchuserexpandfield', { |
||||
expandFieldId: { |
||||
autoIncrement: true, |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
primaryKey: true, |
||||
comment: "拓展字段ID" |
||||
}, |
||||
fieldIdentify: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false, |
||||
comment: "字段标识" |
||||
}, |
||||
displayName: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false, |
||||
comment: "字段名" |
||||
}, |
||||
fieldDescribe: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false, |
||||
comment: "字段描述" |
||||
}, |
||||
defaultValue: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: true, |
||||
comment: "默认值" |
||||
}, |
||||
storageType: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false, |
||||
comment: "存储类型" |
||||
}, |
||||
storageLength: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: "存储长度" |
||||
}, |
||||
isRequired: { |
||||
type: DataTypes.BOOLEAN, |
||||
allowNull: true, |
||||
comment: "是否必填" |
||||
}, |
||||
isRepeat: { |
||||
type: DataTypes.BOOLEAN, |
||||
allowNull: true, |
||||
comment: "是否可以重复" |
||||
}, |
||||
isEnable: { |
||||
type: DataTypes.BOOLEAN, |
||||
allowNull: true, |
||||
comment: "是否启用" |
||||
}, |
||||
createTime: { |
||||
type: DataTypes.DATE, |
||||
allowNull: false, |
||||
comment: "创建时间" |
||||
}, |
||||
isDelete: { |
||||
type: DataTypes.STRING(64), |
||||
allowNull: false, |
||||
defaultValue: "", |
||||
comment: "删除时间" |
||||
} |
||||
}, { |
||||
sequelize, |
||||
tableName: 'lauchuserexpandfield', |
||||
timestamps: false, |
||||
indexes: [ |
||||
{ |
||||
name: "PRIMARY", |
||||
unique: true, |
||||
using: "BTREE", |
||||
fields: [ |
||||
{ name: "expandFieldId" }, |
||||
] |
||||
}, |
||||
] |
||||
}); |
||||
}; |
@ -0,0 +1,52 @@ |
||||
const Sequelize = require('sequelize'); |
||||
module.exports = function(sequelize, DataTypes) { |
||||
return sequelize.define('lauchuserexpandrecord', { |
||||
expandRecordId: { |
||||
autoIncrement: true, |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
primaryKey: true, |
||||
comment: "记录ID" |
||||
}, |
||||
uuid: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: "用户ID" |
||||
}, |
||||
expandField: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false, |
||||
comment: "多占字段ID" |
||||
}, |
||||
expandFieldValue: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false, |
||||
comment: "拓展字段值" |
||||
}, |
||||
createTime: { |
||||
type: DataTypes.DATE, |
||||
allowNull: false, |
||||
comment: "创建时间" |
||||
}, |
||||
isDelete: { |
||||
type: DataTypes.STRING(64), |
||||
allowNull: false, |
||||
defaultValue: "", |
||||
comment: "删除时间" |
||||
} |
||||
}, { |
||||
sequelize, |
||||
tableName: 'lauchuserexpandrecord', |
||||
timestamps: false, |
||||
indexes: [ |
||||
{ |
||||
name: "PRIMARY", |
||||
unique: true, |
||||
using: "BTREE", |
||||
fields: [ |
||||
{ name: "expandRecordId" }, |
||||
] |
||||
}, |
||||
] |
||||
}); |
||||
}; |
@ -0,0 +1,37 @@ |
||||
const Sequelize = require('sequelize'); |
||||
module.exports = function(sequelize, DataTypes) { |
||||
return sequelize.define('lauchuserloginrecord', { |
||||
loginRecordId: { |
||||
autoIncrement: true, |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
primaryKey: true |
||||
}, |
||||
uuid: { |
||||
type: DataTypes.UUID, |
||||
allowNull: false |
||||
}, |
||||
recoredJson: { |
||||
type: DataTypes.TEXT, |
||||
allowNull: false |
||||
}, |
||||
createTime: { |
||||
type: DataTypes.DATE, |
||||
allowNull: false |
||||
} |
||||
}, { |
||||
sequelize, |
||||
tableName: 'lauchuserloginrecord', |
||||
timestamps: false, |
||||
indexes: [ |
||||
{ |
||||
name: "PRIMARY", |
||||
unique: true, |
||||
using: "BTREE", |
||||
fields: [ |
||||
{ name: "loginRecordId" }, |
||||
] |
||||
}, |
||||
] |
||||
}); |
||||
}; |
@ -0,0 +1,41 @@ |
||||
const Sequelize = require('sequelize'); |
||||
module.exports = function(sequelize, DataTypes) { |
||||
return sequelize.define('lauchuserpasswd', { |
||||
passwdId: { |
||||
autoIncrement: true, |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
primaryKey: true, |
||||
comment: "密码记录ID" |
||||
}, |
||||
uuid: { |
||||
type: DataTypes.UUID, |
||||
allowNull: false, |
||||
comment: "用户ID" |
||||
}, |
||||
passwd: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false, |
||||
comment: "用户密码" |
||||
}, |
||||
createTime: { |
||||
type: DataTypes.DATE, |
||||
allowNull: false, |
||||
comment: "创建时间" |
||||
} |
||||
}, { |
||||
sequelize, |
||||
tableName: 'lauchuserpasswd', |
||||
timestamps: false, |
||||
indexes: [ |
||||
{ |
||||
name: "PRIMARY", |
||||
unique: true, |
||||
using: "BTREE", |
||||
fields: [ |
||||
{ name: "passwdId" }, |
||||
] |
||||
}, |
||||
] |
||||
}); |
||||
}; |
@ -0,0 +1,64 @@ |
||||
const Sequelize = require('sequelize'); |
||||
module.exports = function(sequelize, DataTypes) { |
||||
return sequelize.define('lauchuserstructauthority', { |
||||
authorityStructId: { |
||||
autoIncrement: true, |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
primaryKey: true |
||||
}, |
||||
authorityType: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false, |
||||
comment: "按钮、页面、接口" |
||||
}, |
||||
authorityName: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false |
||||
}, |
||||
authorityIdentify: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false |
||||
}, |
||||
authorityDescribe: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false |
||||
}, |
||||
father: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false |
||||
}, |
||||
avatar: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false |
||||
}, |
||||
status: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false, |
||||
comment: "禁用启用" |
||||
}, |
||||
createTime: { |
||||
type: DataTypes.DATE, |
||||
allowNull: false |
||||
}, |
||||
isDelete: { |
||||
type: DataTypes.STRING(64), |
||||
allowNull: false, |
||||
defaultValue: "" |
||||
} |
||||
}, { |
||||
sequelize, |
||||
tableName: 'lauchuserstructauthority', |
||||
timestamps: false, |
||||
indexes: [ |
||||
{ |
||||
name: "PRIMARY", |
||||
unique: true, |
||||
using: "BTREE", |
||||
fields: [ |
||||
{ name: "authorityStructId" }, |
||||
] |
||||
}, |
||||
] |
||||
}); |
||||
}; |
@ -0,0 +1,54 @@ |
||||
const Sequelize = require('sequelize'); |
||||
module.exports = function(sequelize, DataTypes) { |
||||
return sequelize.define('lauchuserstructorganization', { |
||||
organizationStructId: { |
||||
autoIncrement: true, |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
primaryKey: true |
||||
}, |
||||
organizationType: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false |
||||
}, |
||||
organizationName: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false |
||||
}, |
||||
organizationDescribe: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false |
||||
}, |
||||
father: { |
||||
type: DataTypes.INTEGER.UNSIGNED.ZEROFILL, |
||||
allowNull: false |
||||
}, |
||||
isDefault: { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false |
||||
}, |
||||
createTime: { |
||||
type: DataTypes.DATE, |
||||
allowNull: false |
||||
}, |
||||
isDelete: { |
||||
type: DataTypes.STRING(64), |
||||
allowNull: false, |
||||
defaultValue: "" |
||||
} |
||||
}, { |
||||
sequelize, |
||||
tableName: 'lauchuserstructorganization', |
||||
timestamps: false, |
||||
indexes: [ |
||||
{ |
||||
name: "PRIMARY", |
||||
unique: true, |
||||
using: "BTREE", |
||||
fields: [ |
||||
{ name: "organizationStructId" }, |
||||
] |
||||
}, |
||||
] |
||||
}); |
||||
}; |
@ -0,0 +1,42 @@ |
||||
const Sequelize = require('sequelize'); |
||||
module.exports = function(sequelize, DataTypes) { |
||||
return sequelize.define('lauchuserstructrelationorganizationaccent', { |
||||
organizationRelationAccentId: { |
||||
autoIncrement: true, |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
primaryKey: true |
||||
}, |
||||
uuid: { |
||||
type: DataTypes.UUID, |
||||
allowNull: false |
||||
}, |
||||
organizationStruct: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false |
||||
}, |
||||
createTime: { |
||||
type: DataTypes.DATE, |
||||
allowNull: false |
||||
}, |
||||
isDelete: { |
||||
type: DataTypes.STRING(64), |
||||
allowNull: false, |
||||
defaultValue: "" |
||||
} |
||||
}, { |
||||
sequelize, |
||||
tableName: 'lauchuserstructrelationorganizationaccent', |
||||
timestamps: false, |
||||
indexes: [ |
||||
{ |
||||
name: "PRIMARY", |
||||
unique: true, |
||||
using: "BTREE", |
||||
fields: [ |
||||
{ name: "organizationRelationAccentId" }, |
||||
] |
||||
}, |
||||
] |
||||
}); |
||||
}; |
@ -0,0 +1,42 @@ |
||||
const Sequelize = require('sequelize'); |
||||
module.exports = function(sequelize, DataTypes) { |
||||
return sequelize.define('lauchuserstructrelationorganizationauthority', { |
||||
organizationRelationAuthorityId: { |
||||
autoIncrement: true, |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
primaryKey: true |
||||
}, |
||||
organizationStruct: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false |
||||
}, |
||||
authorityStruct: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false |
||||
}, |
||||
createTime: { |
||||
type: DataTypes.DATE, |
||||
allowNull: false |
||||
}, |
||||
isDelete: { |
||||
type: DataTypes.STRING(64), |
||||
allowNull: false, |
||||
defaultValue: "" |
||||
} |
||||
}, { |
||||
sequelize, |
||||
tableName: 'lauchuserstructrelationorganizationauthority', |
||||
timestamps: false, |
||||
indexes: [ |
||||
{ |
||||
name: "PRIMARY", |
||||
unique: true, |
||||
using: "BTREE", |
||||
fields: [ |
||||
{ name: "organizationRelationAuthorityId" }, |
||||
] |
||||
}, |
||||
] |
||||
}); |
||||
}; |
@ -0,0 +1 @@ |
||||
sequelize-auto -h 数据库的IP地址 -d 数据库名 -u 用户名 -x 密码 -p 端口 -t 表名 |
Binary file not shown.
@ -0,0 +1,58 @@ |
||||
{ |
||||
"name": "initkoa", |
||||
"version": "1.0.0", |
||||
"description": "", |
||||
"type": "module", |
||||
"main": "app.js", |
||||
"imports": { |
||||
"#root/*": "./*", |
||||
"#home/*": "./src/*", |
||||
"#routes/*": "./src/routes/*", |
||||
"#common/*": "./src/common/*", |
||||
"#dataModels/*": "./src/common/database/dataModels/*", |
||||
"#cache/*": "./src/cache/*", |
||||
"#config": "./config/config.js", |
||||
"#task/*": "./src/task/*", |
||||
"#workers/*": "./src/workers/*", |
||||
"#protocol/*": "./src/protocol/*", |
||||
"#processes/*": "./src/processes/*" |
||||
}, |
||||
"scripts": { |
||||
"test": "echo \"Error: no test specified\" && exit 1", |
||||
"format": "prettier --write \"src/**/*.js\"", |
||||
"start": "cross-env NODE_ENV=production node bootstrap.js", |
||||
"start:dev": "cross-env NODE_ENV=development nodemon --unhandled-rejections=throw bootstrap.js", |
||||
"start:testDev": "cross-env NODE_ENV=development node bootstrap.js", |
||||
"genmodel": "cd gen-Model && sequelize-auto -h 127.0.0.1 -d lauch -u root -x root -p 3306" |
||||
}, |
||||
"keywords": [], |
||||
"author": "", |
||||
"license": "ISC", |
||||
"dependencies": { |
||||
"ajv": "^8.12.0", |
||||
"ajv-errors": "^3.0.0", |
||||
"koa": "^2.14.2", |
||||
"koa-body": "^6.0.1", |
||||
"koa-compress": "^5.1.1", |
||||
"koa-json-error": "^3.1.2", |
||||
"koa-logger": "^3.2.1", |
||||
"koa-ratelimit": "^5.0.1", |
||||
"koa-router": "^12.0.1", |
||||
"koa-useragent": "^4.1.0", |
||||
"mysql2": "^3.6.5", |
||||
"sequelize": "^6.35.1", |
||||
"winston": "^3.11.0", |
||||
"winston-daily-rotate-file": "^4.7.1" |
||||
}, |
||||
"devDependencies": { |
||||
"axios": "^1.6.2", |
||||
"cross-env": "^7.0.3", |
||||
"eslint": "^8.52.0", |
||||
"eslint-config-prettier": "^9.0.0", |
||||
"eslint-plugin-prettier": "^5.0.0", |
||||
"loadtest": "^8.0.5", |
||||
"nodemon": "^3.0.1", |
||||
"prettier": "^3.0.3", |
||||
"sequelize-auto": "^0.8.8" |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,43 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: initkoa
|
||||
// | @文件描述: production.env.js -
|
||||
// | @创建时间: 2023-11-25 21:36
|
||||
// | @更新时间: 2023-11-25 21:36
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
|
||||
const prodConfig = { |
||||
appName: 'graphResource2', |
||||
port: 6000, |
||||
zip: true, |
||||
upfile:{ |
||||
maxFileSize: 200 * 1024 * 1024, |
||||
hashAlgorithm: false, // md5 sha1 sha256 sha512
|
||||
}, |
||||
request:{ |
||||
maxFieldsSize: 2 * 1024 * 1024,// 请求体大小
|
||||
}, |
||||
ratelimit:{ |
||||
// 同设备(IP)限制登陆次数
|
||||
status: true, |
||||
duration: 60 * 1000, |
||||
max: 1000000, |
||||
}, |
||||
database:{ |
||||
mysql:{ |
||||
host: '127.0.0.1', |
||||
port: 3306, |
||||
username: 'root', |
||||
password: 'root', |
||||
database: 'graph_resource2' |
||||
} |
||||
} |
||||
} |
||||
|
||||
export default prodConfig |
@ -0,0 +1,136 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: initkoa
|
||||
// | @文件描述: app.js - Koa项目的入口文件
|
||||
// | @创建时间: 2023-11-25 20:58
|
||||
// | @更新时间: 2023-11-25 20:58
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
import Koa from 'koa'; |
||||
import koaLogger from 'koa-logger'; |
||||
import compress from 'koa-compress'; |
||||
import ratelimit from 'koa-ratelimit'; |
||||
import handleError from 'koa-json-error'; |
||||
import { koaBody } from 'koa-body'; |
||||
import { userAgent } from 'koa-useragent'; |
||||
|
||||
import rootRouter from '#routes/index.js'; |
||||
|
||||
export default function startApp() { |
||||
const app = new Koa(); |
||||
if (global.ENV === 'development') { |
||||
// | 开启自带日志
|
||||
app.use(koaLogger()); |
||||
} |
||||
|
||||
if (config.ratelimit.status) { |
||||
// | 限制同一用户的频繁请求
|
||||
app.use( |
||||
ratelimit({ |
||||
driver: 'memory', // 存储限流数据的驱动,这里使用内存驱动
|
||||
db: new Map(), // 存储被限制的客户端信息的数据结构
|
||||
duration: config.ratelimit.duration, // 时间窗口,单位毫秒
|
||||
max: config.ratelimit.max, // 时间窗口内允许的最大请求数量
|
||||
id: (ctx) => ctx.ip, // 提取每个请求的唯一标识符,默认使用请求的 IP 地址
|
||||
}), |
||||
); |
||||
} |
||||
|
||||
app.use( |
||||
handleError({ |
||||
format: (err) => { |
||||
// 返回错误的格式
|
||||
switch (err.status) { |
||||
case 400: { |
||||
return { |
||||
code: err.status, |
||||
message: err.e, |
||||
}; |
||||
break; |
||||
} |
||||
default: { |
||||
return { |
||||
code: err.status, |
||||
message: err.message, |
||||
result: ENV === 'development' && err.stack, |
||||
}; |
||||
} |
||||
} |
||||
}, |
||||
postFormat: (err, obj) => { |
||||
//根据不同环境,返回不同格式的错误信息
|
||||
const { result, ...rest } = obj; |
||||
return process.env.NODE_ENV == 'production' ? rest : obj; |
||||
}, |
||||
}), |
||||
); |
||||
|
||||
// 响应封装中间件
|
||||
async function responseHandler(ctx, next) { |
||||
// 执行后续中间件
|
||||
await next(); |
||||
// 如果有响应且没有错误状态码
|
||||
if (ctx.response.is('json') && ![404, 204].includes(ctx.status)) { |
||||
// 封装响应体为标准格式
|
||||
ctx.body = { |
||||
code: ctx.status, |
||||
success: true, |
||||
data: ctx.body, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
// 在路由之前加载响应封装中间件
|
||||
app.use(responseHandler); |
||||
|
||||
if (global.zip === true) { |
||||
// | koa-compress 是一个 Koa 中间件,用于压缩 HTTP 响应。使用该中间件可减少 HTTP 响应的大小,从而提升应用程序的性能。
|
||||
app.use(compress()); |
||||
} |
||||
app.use(userAgent); |
||||
|
||||
app.use( |
||||
koaBody({ |
||||
multipart: true, // 支持文件上传
|
||||
detectJSON: true, |
||||
gzip: true, |
||||
// encoding: 'gzip',
|
||||
formidable: { |
||||
// uploadDir:path.join(__dirname,'public/upload/'), // 设置文件上传目录
|
||||
keepExtensions: true, // 保持文件的后缀
|
||||
maxFileSize: config.upfile.maxFileSize, // 文件上传大小
|
||||
maxFieldsSize: config.request.maxFieldsSize, // 除文件外的数据大小
|
||||
onFileBegin: (name, file) => { |
||||
// 文件上传前的设置
|
||||
}, |
||||
hashAlgorithm: config.upfile.hashAlgorithm, |
||||
}, |
||||
}), |
||||
); |
||||
app.use(async (ctx, next) => { |
||||
// 解析查询参数为空字符串的情况
|
||||
const query = ctx.request.query; |
||||
Object.keys(query).forEach(key => { |
||||
if (query[key] === '') { |
||||
query[key] = undefined; |
||||
} |
||||
}); |
||||
|
||||
await next(); |
||||
}); |
||||
|
||||
app.use(rootRouter.routes()); |
||||
app.use(rootRouter.allowedMethods()); |
||||
|
||||
// console.log(rootRouter)
|
||||
|
||||
const routes = rootRouter.stack.map((route) => route.path); |
||||
// console.log(routes);
|
||||
|
||||
return app; |
||||
} |
@ -0,0 +1,787 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: graphResource2
|
||||
// | @文件描述: index.js -
|
||||
// | @创建时间: 2023-12-01 21:38
|
||||
// | @更新时间: 2023-12-01 21:38
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
import { DataTypes, Model, Op } from 'sequelize'; |
||||
import { makeTreeForList } from '#common/tools/makeTree.js'; |
||||
import makeObject from '#common/tools/makeObject.js'; |
||||
|
||||
// 数据库字段类型对照
|
||||
const SQLType = { |
||||
0: { |
||||
name: '字符串', |
||||
identify: 'string', |
||||
length: [0, 4096], |
||||
sequelizeType: DataTypes.STRING, |
||||
}, |
||||
1: { |
||||
name: '布尔值', |
||||
identify: 'boolean', |
||||
length: null, |
||||
sequelizeType: DataTypes.BOOLEAN, |
||||
}, |
||||
2: { |
||||
name: '整数', |
||||
identify: 'integer', |
||||
length: null, |
||||
sequelizeType: DataTypes.INTEGER, |
||||
}, |
||||
3: { |
||||
name: '数字', |
||||
identify: 'double', |
||||
length: null, |
||||
sequelizeType: DataTypes.DOUBLE, |
||||
}, |
||||
}; |
||||
// @ excludeFieldName - Object - 描述: 数据库排除字段
|
||||
const excludeFieldName = [ |
||||
[ |
||||
'entityid', |
||||
'uniquereference', |
||||
'originnode', |
||||
'creator', |
||||
'isdelete', |
||||
'entityname', |
||||
'createtimestamp', |
||||
'updatetimestamp', |
||||
], |
||||
]; |
||||
// @ sequelizeFindAllType - Object - 描述: 查询参数
|
||||
const sequelizeFindAllType = { |
||||
raw: true, // 原始数据
|
||||
mapToModel: true, // 将下划线变成驼峰
|
||||
}; |
||||
|
||||
export async function createCatch(sequelize) { |
||||
global.resourceCache = {}; |
||||
// atomModelCache
|
||||
const atomModelPoolStartTime = performance.now(); |
||||
const atomModelPool = await makeAtomModelCache(sequelize); |
||||
global.resourceCache.atomModelPool = atomModelPool; |
||||
const atomModelPoolEndTime = performance.now(); |
||||
logger.fatal( |
||||
`${'元分类/模型缓存加载完毕: atomModelPool'.padEnd(30, ' ')} | ${ |
||||
atomModelPoolEndTime - atomModelPoolStartTime |
||||
} ms`,
|
||||
); |
||||
|
||||
// atomModelCache
|
||||
const baseDictPoolStartTime = performance.now(); |
||||
const baseDictPool = await makeBaseDictCache(sequelize); |
||||
global.resourceCache.baseDictPool = baseDictPool; |
||||
const baseDictPoolEndTime = performance.now(); |
||||
logger.fatal( |
||||
`${'数据字典缓存加载完毕: baseDictPool'.padEnd(30, ' ')} | ${ |
||||
baseDictPoolEndTime - baseDictPoolStartTime |
||||
} ms`,
|
||||
); |
||||
|
||||
// resourceCache
|
||||
const resourcePoolStartTime = performance.now(); |
||||
const resourcePool = await makeResourceCache(sequelize); |
||||
global.resourceCache.resourcePool = resourcePool; |
||||
const resourcePoolEndTime = performance.now(); |
||||
logger.fatal( |
||||
`${'资源数据缓存加载完毕: resourcePool'.padEnd(30, ' ')} | ${ |
||||
resourcePoolEndTime - resourcePoolStartTime |
||||
} ms`,
|
||||
); |
||||
} |
||||
|
||||
async function makeAtomModelCache(sequelize) { |
||||
const atomModelList = await sequelize.models.AtomModel.findAll({ |
||||
// attributes:['*'],
|
||||
// where: {
|
||||
// isDelete:{
|
||||
// [Op.is]: null,
|
||||
// }
|
||||
// },
|
||||
raw: true, // 原始数据
|
||||
mapToModel: true, // 将下划线变成驼峰
|
||||
}); |
||||
const objectData = {}; |
||||
for (let i of atomModelList) { |
||||
objectData[i.atomModelId] = i; |
||||
} |
||||
const atomModelPool = { |
||||
length: atomModelList.length, |
||||
createtime: new Date().getTime(), |
||||
updatetime: new Date().getTime(), |
||||
allData: atomModelList, |
||||
normalData: atomModelList.filter((i) => !i.isDelete), |
||||
deleteData: atomModelList.filter((i) => i.isDelete), |
||||
objectData, |
||||
}; |
||||
return atomModelPool; |
||||
} |
||||
|
||||
async function makeBaseDictCache(sequelize) { |
||||
const baseDictList = await sequelize.models.BaseDict.findAll({ |
||||
// attributes:['*'],
|
||||
// where: {
|
||||
// isDelete:{
|
||||
// [Op.is]: null,
|
||||
// }
|
||||
// },
|
||||
raw: true, // 原始数据
|
||||
mapToModel: true, // 将下划线变成驼峰
|
||||
}); |
||||
const atomAllList = [], // 原始全量列表
|
||||
atomNormalList = [], // 原始正常数据列表
|
||||
atomDeleteList = [], // 原始删除数据列表
|
||||
atomObject = { |
||||
0: { |
||||
children: [], |
||||
delChildren: [], |
||||
}, |
||||
}, // 包含根节点的原始字典关系对象
|
||||
reduceObject = { |
||||
0: { |
||||
children: [], |
||||
delChildren: [], |
||||
}, |
||||
}, // 包含根节点的精简字典关系对象
|
||||
atomModelObject = { |
||||
0: { |
||||
baseDictTree: [], |
||||
reduceBaseDictTree: [], |
||||
}, |
||||
}; // 元分类/模型分类下的树
|
||||
for (let i = 0; i < baseDictList.length; i++) { |
||||
const item = baseDictList[i]; |
||||
item.children = []; |
||||
item.delChildren = []; |
||||
atomObject[item.baseDictId] = item; |
||||
reduceObject[item.baseDictId] = { |
||||
atomModel: item.atomModel, |
||||
baseDictId: item.baseDictId, |
||||
baseDictName: item.baseDictName, |
||||
baseDictIdentify: item.baseDictIdentify, |
||||
baseDictDescribe: item.baseDictDescribe, |
||||
baseDictFather: item.baseDictFather, |
||||
children: [], |
||||
delChildren: [], |
||||
}; |
||||
// 以上建立精简数据和原始数据
|
||||
atomAllList.push(item); |
||||
if (item.isDelete) { |
||||
atomDeleteList.push(item); |
||||
} else { |
||||
atomNormalList.push(item); |
||||
} |
||||
// 以上将数据分类放入列表
|
||||
} |
||||
for (let i = 0; i < atomNormalList.length; i++) { |
||||
// 正常数据
|
||||
const dictId = atomNormalList[i].baseDictId; // 字典项ID
|
||||
const fatherId = atomNormalList[i].baseDictFather; // 父ID
|
||||
const aData = atomNormalList[i]; // 原始数据字典项
|
||||
const rData = reduceObject[dictId]; // 精简字典项
|
||||
atomObject[fatherId].children.push(aData); // 压入原始数据到原始对象
|
||||
reduceObject[fatherId].children.push(rData); // 压入精简数据到精简对象
|
||||
} |
||||
for (let i = 0; i < atomDeleteList.length; i++) { |
||||
// 已删除数据
|
||||
const dictId = atomDeleteList[i].baseDictId; // 字典项ID
|
||||
const fatherId = atomDeleteList[i].baseDictFather; // 父ID
|
||||
const aData = atomDeleteList[i]; // 原始数据字典项
|
||||
const rData = reduceObject[dictId]; // 精简字典项
|
||||
atomObject[fatherId].delChildren.push(aData); // 压入原始数据到原始对象
|
||||
reduceObject[fatherId].delChildren.push(rData); // 压入精简数据到精简对象
|
||||
} |
||||
const atomModelList = resourceCache.atomModelPool.normalData; |
||||
for (let i = 0; i < atomModelList.length; i++) { |
||||
const atomModel = atomModelList[i]; |
||||
const atomModelId = atomModel.atomModelId; |
||||
atomModelObject[atomModelId] = { |
||||
baseDictTree: [], |
||||
reduceBaseDictTree: [], |
||||
}; |
||||
atomModelObject[atomModelId].baseDictTree = atomObject[ |
||||
'0' |
||||
].children.filter( |
||||
(dict) => !(dict.atomModel && atomModelId != dict.atomModel), |
||||
); |
||||
atomModelObject[atomModelId].reduceBaseDictTree = reduceObject[ |
||||
'0' |
||||
].children.filter( |
||||
(dict) => !(dict.atomModel && atomModelId != dict.atomModel), |
||||
); |
||||
} |
||||
const baseDictPool = { |
||||
length: baseDictList.length, |
||||
createtime: new Date().getTime(), |
||||
updatetime: new Date().getTime(), |
||||
atomAllList, |
||||
atomNormalList, |
||||
atomDeleteList, |
||||
atomObject, |
||||
reduceObject, |
||||
atomModelObject, |
||||
}; |
||||
return baseDictPool; |
||||
} |
||||
|
||||
async function makeResourceCache(sequelize) { |
||||
// @ RAP - Object - 描述:元分类/模型缓存池
|
||||
const RAP = resourceCache.atomModelPool; |
||||
// @ RBP - Object - 描述:字典缓存池
|
||||
const RBP = resourceCache.baseDictPool; |
||||
// ! 获取资源类信息 =============================================================================
|
||||
console.time('获取资源类信息'); |
||||
// region 获取资源类信息
|
||||
const classList = await sequelize.models.ResourceClassBase.findAll({ |
||||
...sequelizeFindAllType, |
||||
where: { |
||||
isDelete: { |
||||
[Op.is]: null, |
||||
}, |
||||
}, |
||||
}); |
||||
// @ classListForAtomModel - String - 描述:按照元分类/模型ID区分资源类
|
||||
const classListForAtomModel = {}; |
||||
for(let atomModel of RAP.normalData){ |
||||
classListForAtomModel[atomModel.atomModelId] = [] |
||||
} |
||||
// @ classIdList - Array[number] - 描述:资源类ID列表
|
||||
const classIdList = []; |
||||
// @ classObject - Object[resourceClassBase] - 描述:资源类对象
|
||||
const classObject = {}; |
||||
for (let i = 0; i < classList.length; i++) { |
||||
// 资源类对象
|
||||
const classItem = classList[i]; |
||||
// 资源类ID
|
||||
const classId = classList[i].resourceClassBaseId; |
||||
// 将资源类ID放入资源类ID列表
|
||||
classIdList.push(classId); |
||||
// 将资源类对象放入集合中
|
||||
classObject[classId] = classItem; |
||||
// 读取atomModel,元分类/模型信息
|
||||
const atomModelId = classItem.atomModel; |
||||
// 从原分类/模型中拿到其名称
|
||||
classItem.atomModelName = RAP.objectData[atomModelId].atomModelName; |
||||
// 读取resourceClassBaseDefine, 资源类定义catch提的,目前缺省
|
||||
// | 待补充
|
||||
// 读取resourceClassBaseType,资源类型定义,0实体、1虚拟、2管理层级
|
||||
classItem.resourceClassBaseTypeName = { |
||||
0: '实体资源类', |
||||
1: '虚拟资源类', |
||||
2: '层级管理资源类', |
||||
}[classItem.resourceClassBaseType]; |
||||
if(classListForAtomModel[classItem.atomModel] === undefined){ |
||||
classListForAtomModel[classItem.atomModel] = [] |
||||
} |
||||
classListForAtomModel[classItem.atomModel].push(classItem) |
||||
} |
||||
// console.log(classIdList)
|
||||
// console.log(classObject)
|
||||
// endregion
|
||||
console.timeEnd('获取资源类信息'); |
||||
// ! 获取拓展字段信息 ===========================================================================
|
||||
console.time('获取拓展字段信息'); |
||||
// region 获取拓展字段信息
|
||||
const classExpandList = |
||||
await sequelize.models.ResourceClassExpandField.findAll( |
||||
sequelizeFindAllType, |
||||
); |
||||
// @ classExpandIdList - Array[number] - 描述:资源类拓展字段ID列表
|
||||
const classExpandIdList = []; |
||||
// @ classExpandObject - Object[resourceClassExpandField] - 描述:资源类拓展字段对象
|
||||
const classExpandObject = {}; |
||||
// @ classExpandForClassBaseObject - Object - 描述:基于资源类ID的资源拓展字段对象集合
|
||||
const classExpandForClassBaseObject = {}; |
||||
for (let i = 0; i < classExpandList.length; i++) { |
||||
const expandField = classExpandList[i]; |
||||
if (expandField.isDelete) { |
||||
// 如果删除
|
||||
continue; |
||||
} |
||||
const expandFieldId = expandField.resourceClassExpandFieldId; |
||||
if (expandField.resourceClassExpandFieldRelationType === 0) { |
||||
// ! 如果是字典值
|
||||
expandField.resourceClassExpandFieldRelation = |
||||
RBP.atomObject[ |
||||
expandField.resourceClassExpandFieldValue |
||||
].baseDictName; |
||||
} else if (expandField.resourceClassExpandFieldRelationType === 1) { |
||||
// ! 如果是资源类
|
||||
expandField.resourceClassExpandFieldRelation = |
||||
classObject[ |
||||
expandField.resourceClassExpandFieldIdentify |
||||
].resourceClassBaseName; |
||||
} else { |
||||
expandField.resourceClassExpandFieldRelation = null; |
||||
} |
||||
classExpandIdList.push(expandFieldId); |
||||
classExpandObject[expandFieldId] = expandField; |
||||
// 拓展字段中的资源类ID
|
||||
const classId = expandField.resourceClassBase; |
||||
if (classExpandForClassBaseObject[classId] === undefined) { |
||||
classExpandForClassBaseObject[classId] = { |
||||
[expandField.resourceClassExpandFieldIdentify]: expandField, |
||||
}; |
||||
} else { |
||||
classExpandForClassBaseObject[classId][ |
||||
expandField.resourceClassExpandFieldIdentify |
||||
] = expandField; |
||||
} |
||||
} |
||||
// console.log(classExpandForClassBaseObject)
|
||||
// endregion
|
||||
console.timeEnd('获取拓展字段信息'); |
||||
// ! 获取实体结构表 =============================================================================
|
||||
console.time('获取实体结构表'); |
||||
// region 获取实体结构表
|
||||
const entityStructList = |
||||
await sequelize.models.ResourceEntityStruct.findAll( |
||||
sequelizeFindAllType, |
||||
); |
||||
// @ entityStructIdList - Array[number] - 描述:资源实体结构id列表
|
||||
const entityStructIdList = []; |
||||
// @ entityStructObject - Object[resourceEntityStruct] - 描述:资源实体结构对象集合
|
||||
const entityStructObject = {}; |
||||
// @ entityStructForClassBaseObject - Object - 描述:基于资源类ID的资源实体字段对象集合
|
||||
const entityStructForClassBaseObject = {}; |
||||
for (let i = 0; i < entityStructList.length; i++) { |
||||
const entityStruct = entityStructList[i]; |
||||
if (entityStruct.isDelete) { |
||||
// 如果删除
|
||||
continue; |
||||
} |
||||
const entityStructId = entityStruct.resourceEntityStructId; |
||||
entityStructIdList.push(entityStructId); |
||||
entityStructObject[entityStructId] = entityStruct; |
||||
// 获取存储类型和长度
|
||||
entityStruct.storageTypeValue = |
||||
SQLType[entityStruct.resourceEntityStructStorageType]; |
||||
// 这里的关联值不做处理
|
||||
// 拓展字段中的资源类ID
|
||||
const classId = entityStruct.resourceClassBase; |
||||
if (entityStructForClassBaseObject[classId] === undefined) { |
||||
entityStructForClassBaseObject[classId] = { |
||||
[entityStruct.resourceEntityStructIdentify]: entityStruct, |
||||
}; |
||||
} else { |
||||
entityStructForClassBaseObject[classId][ |
||||
entityStruct.resourceEntityStructIdentify |
||||
] = entityStruct; |
||||
} |
||||
} |
||||
// endregion
|
||||
console.timeEnd('获取实体结构表'); |
||||
// ! 获取资源类关系表 ===========================================================================
|
||||
console.time('获取资源类关系表'); |
||||
// region 获取资源类关系表
|
||||
const classRelationList = |
||||
await sequelize.models.ResourceClassRelation.findAll( |
||||
{...sequelizeFindAllType,where: { |
||||
isDelete: { |
||||
[Op.is]: null, |
||||
}, |
||||
},}, |
||||
); |
||||
// @ classRelationIdList - String - 描述:获取资源类关系ID列表
|
||||
const classRelationIdList = []; |
||||
// @ classRelationObject - String - 描述:获取资源类关系对象集合
|
||||
const classRelationObject = {}; |
||||
// @ classRelationObjectForTargetClass - String - 描述:获取资源类关系对象集合继续目标资源类
|
||||
const classRelationObjectForTargetClass = {}; |
||||
for (let i = 0; i < classRelationList.length; i++) { |
||||
const classRelationItem = classRelationList[i]; |
||||
const classRelationItemId = classRelationItem.resourceClassRelationId; |
||||
const targetId = classRelationItem.resourceClassRelationTarget; |
||||
classRelationIdList.push(classRelationItemId); |
||||
classRelationObject[classRelationItemId] = classRelationItem; |
||||
if (classRelationObjectForTargetClass[targetId] === undefined) { |
||||
classRelationObjectForTargetClass[targetId] = [classRelationItem]; |
||||
} else { |
||||
classRelationObjectForTargetClass[targetId].push(classRelationItem); |
||||
} |
||||
} |
||||
// endregion
|
||||
console.timeEnd('获取资源类关系表'); |
||||
|
||||
// | 这里生成基础数据模型 =============================================================================================
|
||||
console.time('==生成资源类基础数据模型=='); |
||||
// region 这里生成基础数据模型
|
||||
// ! 资源类
|
||||
class ClassModel { |
||||
constructor(classItem) { |
||||
// 设置基础信息值
|
||||
this.resourceClass = classItem; |
||||
} |
||||
get classId() { |
||||
return this.resourceClass.resourceClassBaseId; |
||||
} |
||||
get classIdentify() { |
||||
return this.resourceClass.resourceClassBaseIdentify; |
||||
} |
||||
get atomModel() { |
||||
return this.resourceClass.atomModel; |
||||
} |
||||
get className() { |
||||
return this.resourceClass.resourceClassBaseName; |
||||
} |
||||
get classExpand() { |
||||
let classExpand = classExpandForClassBaseObject[this.classId]; |
||||
if (classExpand === undefined) { |
||||
classExpand = {}; |
||||
} |
||||
return classExpand; |
||||
} |
||||
get entityStruct() { |
||||
let entityStruct = entityStructForClassBaseObject[this.classId]; |
||||
if (entityStruct === undefined) { |
||||
entityStruct = {}; |
||||
} |
||||
return entityStruct; |
||||
} |
||||
get classRelationList() { |
||||
if (classRelationObjectForTargetClass[this.classId] === undefined) { |
||||
return []; |
||||
} else { |
||||
return classRelationObjectForTargetClass[this.classId]; |
||||
} |
||||
} |
||||
get classRelationIdList() { |
||||
return this.classRelationList.map((i) => i.resourceClassRelationId); |
||||
} |
||||
async createSequelizeModel() { |
||||
class resourceEntity extends Model {} |
||||
// 定义资源实体名称字段
|
||||
const entityName = { |
||||
type: DataTypes.STRING(255), |
||||
allowNull: false, |
||||
comment: '资源实体名称', |
||||
}; |
||||
const entityStruct = this.entityStruct; |
||||
// 定义资源实体名称字段在数据库的备注
|
||||
if (entityStruct.entityName !== undefined) { |
||||
entityName.comment = |
||||
entityStruct.entityName.resourceEntityStructName + |
||||
'-' + |
||||
entityStruct.entityName.resourceEntityStructDescribe; |
||||
} |
||||
// 定义字段
|
||||
const defineField = { |
||||
// 在这里定义模型属性
|
||||
entityId: { |
||||
type: DataTypes.STRING(64), |
||||
primaryKey: true, |
||||
allowNull: false, |
||||
comment: '资源实体ID', |
||||
}, |
||||
// 自增列
|
||||
uniqueReference: { |
||||
type: DataTypes.INTEGER, |
||||
autoIncrement: true, |
||||
allowNull: false, |
||||
unique: true, // 使得自增值是唯一的
|
||||
comment: '唯一参考', |
||||
}, |
||||
entityName, |
||||
// 所在节点
|
||||
originNode: { |
||||
type: DataTypes.STRING, |
||||
allowNull: false, |
||||
comment: '所在的资源类节点', |
||||
}, |
||||
creator: { |
||||
type: DataTypes.STRING, |
||||
comment: '创建人', |
||||
}, |
||||
isDelete: { |
||||
type: DataTypes.STRING(64), |
||||
comment: '删除时间', |
||||
}, |
||||
}; |
||||
// 定义数据表
|
||||
const defineTable = { |
||||
// 这是其他模型参数
|
||||
sequelize, // 我们需要传递连接实例
|
||||
modelName: this.classIdentify.toLowerCase(), // 我们需要选择模型名称
|
||||
tableName: 'entity_' + this.classIdentify.toLowerCase(), |
||||
comment: '元分类/模型表', |
||||
timestamps: true, // 不要忘记启用时间戳!
|
||||
createdAt: 'createTimestamp', // 不想要 createdAt
|
||||
// 想要 updatedAt 但是希望名称叫做 updateTimestamp
|
||||
updatedAt: 'updateTimestamp', |
||||
underscored: true, // 改成下划线格式
|
||||
}; |
||||
for (let fieldName in this.entityStruct) { |
||||
// 排除固定字段
|
||||
if (excludeFieldName.includes(fieldName.toLowerCase())) { |
||||
continue; |
||||
} |
||||
// 拿到字段详细信息
|
||||
const oneEntityStruct = this.entityStruct[fieldName]; |
||||
// 建立数据库表字段标准数据
|
||||
const oneField = { |
||||
type: DataTypes.STRING, // 对于意外数据,设置默认长度255的字符串
|
||||
comment: |
||||
oneEntityStruct.resourceEntityStructName + |
||||
'-' + |
||||
oneEntityStruct.resourceEntityStructDescribe, |
||||
}; |
||||
// 拿到自定义的存储类型
|
||||
const fieldStorageType = |
||||
this.entityStruct[fieldName] |
||||
.resourceEntityStructStorageType; |
||||
if ( |
||||
Object.keys(SQLType).includes(fieldStorageType.toString()) |
||||
) { |
||||
// 如果该自定义存储类型在预设的存储类型中,设置自定义的存储类型
|
||||
oneField.type = SQLType[fieldStorageType].sequelizeType; |
||||
if ( |
||||
fieldStorageType == 0 && |
||||
oneEntityStruct.resourceEntityStructStorageLength < 4096 |
||||
) { |
||||
// 如果是字符串,并且长度在0 > 4096,设置其长度
|
||||
oneField.type = oneField.type( |
||||
parseInt( |
||||
oneEntityStruct.resourceEntityStructStorageLength, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
// 将该字段放入数据库字段定义对象中
|
||||
defineField[fieldName] = oneField; |
||||
} |
||||
resourceEntity.init(defineField, defineTable); |
||||
// 同步数据库
|
||||
await resourceEntity.sync(); // {alter: true}是修改,现在是如果存在就不更改表,后面修改表明,字段名,字段长度会在更新操作时添加
|
||||
this.sequelizeModel = resourceEntity; |
||||
} |
||||
get zeroInfo(){ |
||||
return { |
||||
resourceClassBaseId: this.resourceClass.resourceClassBaseId, |
||||
resourceClassBaseName: this.resourceClass.resourceClassBaseName, |
||||
resourceClassBaseColor: this.resourceClass.resourceClassBaseColor, |
||||
resourceClassBaseAvatar: this.resourceClass.resourceClassBaseAvatar, |
||||
} |
||||
} |
||||
get info(){ |
||||
return{ |
||||
...this.resourceClass, |
||||
resourceClassExpandField: this.classExpand, |
||||
resourceEntityStruct: this.entityStruct, |
||||
resourceClassRelation: this.classRelationList |
||||
} |
||||
} |
||||
} |
||||
// @ classModelObject - Object - 描述:资源类数据模型对象集合
|
||||
const classModelObject = {}; |
||||
for (let classId of classIdList) { |
||||
if (classObject[classId].isDelete) { |
||||
// 排除删除的
|
||||
continue; |
||||
} |
||||
classModelObject[classId] = new ClassModel(classObject[classId]); |
||||
console.time(' 初始化资源实体表'+ classModelObject[classId].classIdentify) |
||||
await classModelObject[classId].createSequelizeModel(); |
||||
console.timeEnd(' 初始化资源实体表'+ classModelObject[classId].classIdentify) |
||||
} |
||||
// console.log((await sequelize.models.omencust.create({
|
||||
// entityId: 'omencust' + crypto.randomUUID(),
|
||||
// originNode: '0-0-1',
|
||||
// entityName: '中天合创',
|
||||
// address: '鄂尔多斯',
|
||||
// util: '54',
|
||||
// project: '1'
|
||||
// })).dataValues)
|
||||
// endregion
|
||||
console.timeEnd('==生成资源类基础数据模型=='); |
||||
|
||||
// | 这里生成关系节点数据模型 ==========================================================================================
|
||||
console.time('==生成类关系节点数据模型=='); |
||||
// region 这里生成关系节点数据模型
|
||||
// 给每个类生成关联列表
|
||||
// @ classNodeObject - Object - 描述:类关系节点数据模型集合
|
||||
const classNodeObject = { |
||||
0: { |
||||
classModel: {}, |
||||
nodeId: 0, |
||||
children: [], |
||||
father: null, |
||||
}, |
||||
}; |
||||
// ! 资源类节点
|
||||
class ClassNodeModel { |
||||
constructor(classModel, classRelationId) { |
||||
this.nodeId = classRelationId; |
||||
this.fatherId = |
||||
classRelationObject[ |
||||
classRelationId |
||||
].resourceClassRelationFather; |
||||
this.children = []; |
||||
this.classData = classModel; |
||||
} |
||||
get classId() { |
||||
return this.classData.classId; |
||||
} |
||||
get classIdentify() { |
||||
return this.classData.classIdentify; |
||||
} |
||||
get atomModel() { |
||||
return this.classData.atomModel; |
||||
} |
||||
get className() { |
||||
return this.classData.className; |
||||
} |
||||
get classExpand() { |
||||
return this.classData.classExpand; |
||||
} |
||||
get entityStruct() { |
||||
return this.classData.entityStruct; |
||||
} |
||||
get classRelationList() { |
||||
return this.classData.classRelationList; |
||||
} |
||||
get classRelationIdList() { |
||||
return this.classData.classRelationIdList; |
||||
} |
||||
get nodeZeroInfo(){ |
||||
return { |
||||
...this.classData.zeroInfo, |
||||
nodeId: this.nodeId, |
||||
fatherId: this.fatherId, |
||||
children: this.children.map(i => i.nodeZeroInfo) |
||||
} |
||||
} |
||||
} |
||||
// @ 生成类关系节点
|
||||
for (let classId of Object.keys(classModelObject)) { |
||||
const nowClassModel = classModelObject[classId]; |
||||
const nowRelationIdList = nowClassModel.classRelationIdList; |
||||
if (nowRelationIdList.length == 0) { |
||||
// @ 在数据库添加这条关系
|
||||
const newClassRelation = |
||||
await sequelize.models.ResourceClassRelation.create({ |
||||
resourceClassRelationFather: 0, |
||||
resourceClassRelationTarget: classId, |
||||
}); |
||||
const newClassRelationData = newClassRelation.dataValues; |
||||
const newClassRelationDataId = |
||||
newClassRelationData.resourceClassRelationId; |
||||
classRelationList.push(newClassRelationData) |
||||
classRelationIdList.push(newClassRelationDataId); |
||||
classRelationObject[newClassRelationDataId] = newClassRelationData; |
||||
classRelationObjectForTargetClass[classId] = [newClassRelationData]; |
||||
classNodeObject[newClassRelationDataId] = new ClassNodeModel( |
||||
nowClassModel, |
||||
newClassRelationDataId, |
||||
); |
||||
} |
||||
for (let i = 0; i < nowRelationIdList.length; i++) { |
||||
const classRelationId = nowRelationIdList[i]; |
||||
classNodeObject[classRelationId] = new ClassNodeModel( |
||||
nowClassModel, |
||||
classRelationId, |
||||
); |
||||
} |
||||
}; |
||||
// @ 构建类关系
|
||||
for (let i = 0; i < classRelationIdList.length; i++) { |
||||
const nodeId = classRelationList[i].resourceClassRelationId; |
||||
const classNode = classNodeObject[nodeId]; |
||||
const fatherId = classNodeObject[nodeId].fatherId; |
||||
classNodeObject[fatherId].children.push(classNode); |
||||
} |
||||
// console.log(classNodeObject)
|
||||
// @ 根据关联元/模型分类资源类节点
|
||||
// @ classNodeObjectForAtomModel - String - 描述:关联元/模型下的根节点
|
||||
const classNodeObjectForAtomModel = {}; |
||||
// 根据元分类/模型创建根节点的空数组
|
||||
for (let i = 0; i < RAP.normalData.length; i++) { |
||||
classNodeObjectForAtomModel[RAP.normalData[i].atomModelId] = []; |
||||
} |
||||
// 将根节点放入 分类/模型创建根节点的空数组 中
|
||||
for (let i = 0; i < classNodeObject[0].children.length; i++) { |
||||
const nodeModel = classNodeObject[0].children[i]; |
||||
const atomModelId = nodeModel.atomModel; |
||||
if (classNodeObjectForAtomModel[atomModelId] === undefined) { |
||||
logger.warn('存在未找到元分类/模型的资源类:' + nodeModel.classId); |
||||
} else { |
||||
classNodeObjectForAtomModel[atomModelId].push(nodeModel); |
||||
} |
||||
} |
||||
// console.time('GET TREE')
|
||||
// global.test = classNodeObjectForAtomModel[1][0].nodeZeroInfo
|
||||
// console.timeEnd('GET TREE')
|
||||
// console.time('GET TREE JSON')
|
||||
// global.test2 = JSON.stringify(global.test, null, 3)
|
||||
// console.timeEnd('GET TREE JSON')
|
||||
// console.time('GET TREE JSON CONSOLE')
|
||||
// console.log(global.test)
|
||||
// console.timeEnd('GET TREE JSON CONSOLE')
|
||||
console.timeEnd('==生成类关系节点数据模型=='); |
||||
// endregion
|
||||
|
||||
// ! 获取资源实体 ===============================================================================
|
||||
console.time('获取资源实体'); |
||||
// region 获取资源实体
|
||||
// @ entityListForClassId - Object[Array[Object[entity]]] - 描述:实体列表根据资源类ID区分的对象
|
||||
const entityListForClassId = {}; |
||||
// @ entityListForClassIdentify - Object[Array[Object[entity]]] - 描述:实体列表根据资源类Identify区分的对象
|
||||
const entityListForClassIdentify = {}; |
||||
for (let i of Object.keys(classModelObject)) { |
||||
const classModel = classModelObject[i]; |
||||
const classId = classModel.classId; |
||||
const classIdentify = classModel.classIdentify; |
||||
console.time(' 获取资源实体表信息' + classModel.classIdentify); |
||||
const entityList = await classModel.sequelizeModel.findAll({ |
||||
...sequelizeFindAllType, |
||||
where: { |
||||
isDelete: { |
||||
[Op.is]: null, |
||||
}, |
||||
}, |
||||
}); |
||||
console.timeEnd(' 获取资源实体表信息' + classModel.classIdentify); |
||||
entityListForClassId[classId] = entityList; |
||||
entityListForClassIdentify[classIdentify] = entityList; |
||||
} |
||||
// endregion
|
||||
console.timeEnd('获取资源实体'); |
||||
// ! 获取资源实体关系
|
||||
|
||||
return { |
||||
createtime: new Date().getTime(), |
||||
updatetime: new Date().getTime(), |
||||
// 资源类
|
||||
classList, |
||||
classIdList, |
||||
classObject, |
||||
classListForAtomModel, |
||||
// 资源类拓展字段
|
||||
classExpandList, |
||||
classExpandIdList, |
||||
classExpandObject, |
||||
classExpandForClassBaseObject, |
||||
// 资源实体结构
|
||||
entityStructList, |
||||
entityStructIdList, |
||||
entityStructObject, |
||||
entityStructForClassBaseObject, |
||||
// 资源类关系
|
||||
classRelationList, |
||||
classRelationIdList, |
||||
classRelationObject, |
||||
classRelationObjectForTargetClass, |
||||
// 资源类模型
|
||||
ClassModel, |
||||
classModelObject, |
||||
// 资源类节点模型
|
||||
classNodeObject, |
||||
ClassNodeModel, |
||||
classNodeObjectForAtomModel, |
||||
} |
||||
} |
@ -0,0 +1,61 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: graphResource2
|
||||
// | @文件描述: atomModel.dataModel.js -
|
||||
// | @创建时间: 2023-12-01 13:45
|
||||
// | @更新时间: 2023-12-01 13:45
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
import {Model} from 'sequelize'; |
||||
|
||||
export function mountAtomModel(sequelize, DataTypes) { |
||||
class AtomModel extends Model { |
||||
} |
||||
|
||||
AtomModel.init({ |
||||
// 在这里定义模型属性
|
||||
atomModelId: { |
||||
type: DataTypes.INTEGER, |
||||
autoIncrement: true, |
||||
primaryKey: true, |
||||
allowNull: false, |
||||
comment: '元分类/模型ID' |
||||
}, |
||||
atomModelName: { |
||||
type: DataTypes.STRING, |
||||
allowNull: false, |
||||
comment: '元分类/模型名称' |
||||
}, |
||||
atomModelDescribe: { |
||||
type: DataTypes.STRING(2048), |
||||
allowNull: false, |
||||
comment: '元分类/模型描述' |
||||
}, |
||||
creator:{ |
||||
type: DataTypes.STRING, |
||||
comment: "创建人" |
||||
}, |
||||
isDelete:{ |
||||
type: DataTypes.STRING(64), |
||||
comment: "删除时间" |
||||
} |
||||
}, { |
||||
// 这是其他模型参数
|
||||
sequelize, // 我们需要传递连接实例
|
||||
modelName: 'AtomModel', // 我们需要选择模型名称
|
||||
// tableName: 'atom_model',
|
||||
comment: "元分类/模型表", |
||||
timestamps: true, // 不要忘记启用时间戳!
|
||||
createdAt: 'createTimestamp', // 不想要 createdAt
|
||||
// 想要 updatedAt 但是希望名称叫做 updateTimestamp
|
||||
updatedAt: 'updateTimestamp', |
||||
underscored: true, // 改成下划线格式
|
||||
}); |
||||
|
||||
return AtomModel |
||||
} |
@ -0,0 +1,86 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: graphResource2
|
||||
// | @文件描述: baseDict.dataModel.js -
|
||||
// | @创建时间: 2023-12-01 14:37
|
||||
// | @更新时间: 2023-12-01 14:37
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
import {Model} from 'sequelize'; |
||||
|
||||
export function mountBaseDict(sequelize, DataTypes) { |
||||
class BaseDict extends Model { |
||||
} |
||||
|
||||
BaseDict.init({ |
||||
// 在这里定义模型属性
|
||||
baseDictId: { |
||||
type: DataTypes.INTEGER, |
||||
autoIncrement: true, |
||||
primaryKey: true, |
||||
allowNull: false, |
||||
comment: '基础字典Id' |
||||
}, |
||||
atomModel: { |
||||
type: DataTypes.INTEGER, |
||||
comment: '元分类/模型Id' |
||||
}, |
||||
baseDictIsBase: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: '是否为基础字典(0是自定义,1是基础)', |
||||
defaultValue: 0, |
||||
}, |
||||
baseDictOriginType: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: '模型数据来源(0是系统内,1是系统外)', |
||||
defaultValue: 0, |
||||
}, |
||||
baseDictName:{ |
||||
type: DataTypes.STRING, |
||||
allowNull: false, |
||||
comment: "字典项名称" |
||||
}, |
||||
baseDictIdentify:{ |
||||
type: DataTypes.STRING, |
||||
comment: "字典项标识" |
||||
}, |
||||
baseDictDescribe:{ |
||||
type: DataTypes.STRING(2048), |
||||
comment: "字典项名描述" |
||||
}, |
||||
baseDictFather:{ |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: "字典项父级", |
||||
defaultValue: 0, |
||||
}, |
||||
creator:{ |
||||
type: DataTypes.STRING, |
||||
comment: "创建人" |
||||
}, |
||||
isDelete:{ |
||||
type: DataTypes.STRING(64), |
||||
comment: "删除时间" |
||||
} |
||||
}, { |
||||
// 这是其他模型参数
|
||||
sequelize, // 我们需要传递连接实例
|
||||
modelName: 'BaseDict', // 我们需要选择模型名称
|
||||
// tableName: 'baseDict',
|
||||
comment: "基础字典表", |
||||
timestamps: true, // 不要忘记启用时间戳!
|
||||
createdAt: 'createTimestamp', // 不想要 createdAt
|
||||
// 想要 updatedAt 但是希望名称叫做 updateTimestamp
|
||||
updatedAt: 'updateTimestamp', |
||||
underscored: true, // 改成下划线格式
|
||||
}); |
||||
|
||||
return BaseDict |
||||
} |
@ -0,0 +1,103 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: graphResource2
|
||||
// | @文件描述: resourceClassBase.dataModel.js -
|
||||
// | @创建时间: 2023-12-01 14:56
|
||||
// | @更新时间: 2023-12-01 14:56
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: graphResource2
|
||||
// | @文件描述: atomModel.dataModel.js -
|
||||
// | @创建时间: 2023-12-01 13:45
|
||||
// | @更新时间: 2023-12-01 13:45
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
import { Model } from 'sequelize'; |
||||
|
||||
export function mountResourceClassBase(sequelize, DataTypes) { |
||||
class ResourceClassBase extends Model {} |
||||
|
||||
ResourceClassBase.init( |
||||
{ |
||||
// 在这里定义模型属性
|
||||
resourceClassBaseId: { |
||||
type: DataTypes.INTEGER, |
||||
autoIncrement: true, |
||||
primaryKey: true, |
||||
allowNull: false, |
||||
comment: '资源类ID', |
||||
}, |
||||
atomModel: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: '元分类/模型Id', |
||||
}, |
||||
resourceClassBaseDefine:{ |
||||
type: DataTypes.INTEGER, |
||||
comment: "对资源类的定义,方便建立资源结构,来源于基础字典。" |
||||
}, |
||||
resourceClassBaseIdentify:{ |
||||
type: DataTypes.STRING(8), |
||||
allowNull: false, |
||||
comment: '资源类标识', |
||||
}, |
||||
resourceClassBaseName:{ |
||||
type: DataTypes.STRING, |
||||
allowNull: false, |
||||
comment: '资源类名', |
||||
}, |
||||
resourceClassBaseDescribe: { |
||||
type: DataTypes.STRING(2048), |
||||
comment: '资源类描述', |
||||
}, |
||||
resourceClassBaseType:{ |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: "对资源类的分类,0实体、1虚拟、2管理。", |
||||
defaultValue: 0, |
||||
}, |
||||
resourceClassBaseColor:{ |
||||
type: DataTypes.STRING, |
||||
comment: '资源类颜色', |
||||
}, |
||||
resourceClassBaseAvatar:{ |
||||
type: DataTypes.STRING, |
||||
comment: '资源类图标', |
||||
}, |
||||
creator: { |
||||
type: DataTypes.STRING, |
||||
comment: '创建人', |
||||
}, |
||||
isDelete: { |
||||
type: DataTypes.STRING(64), |
||||
comment: '删除时间', |
||||
}, |
||||
}, |
||||
{ |
||||
// 这是其他模型参数
|
||||
sequelize, // 我们需要传递连接实例
|
||||
modelName: 'ResourceClassBase', // 我们需要选择模型名称
|
||||
// tableName: 'ResourceClassBase',
|
||||
comment: '资源类基础表', |
||||
timestamps: true, // 不要忘记启用时间戳!
|
||||
createdAt: 'createTimestamp', // 不想要 createdAt
|
||||
// 想要 updatedAt 但是希望名称叫做 updateTimestamp
|
||||
updatedAt: 'updateTimestamp', |
||||
underscored: true, // 改成下划线格式
|
||||
}, |
||||
); |
||||
|
||||
return ResourceClassBase; |
||||
} |
@ -0,0 +1,83 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: graphResource2
|
||||
// | @文件描述: resourceClassExpandField.dataModel.js -
|
||||
// | @创建时间: 2023-12-01 15:12
|
||||
// | @更新时间: 2023-12-01 15:12
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
import { Model } from 'sequelize'; |
||||
|
||||
export function mountResourceClassExpandField(sequelize, DataTypes) { |
||||
class ResourceClassExpandField extends Model {} |
||||
|
||||
ResourceClassExpandField.init( |
||||
{ |
||||
// 在这里定义模型属性
|
||||
resourceClassExpandFieldId: { |
||||
type: DataTypes.INTEGER, |
||||
autoIncrement: true, |
||||
primaryKey: true, |
||||
allowNull: false, |
||||
comment: '资源类拓展字段ID', |
||||
}, |
||||
resourceClassBase: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: '资源类ID', |
||||
}, |
||||
resourceClassExpandFieldName: { |
||||
type: DataTypes.STRING, |
||||
allowNull: false, |
||||
comment: '资源类拓展字段名', |
||||
}, |
||||
resourceClassExpandFieldIdentify: { |
||||
type: DataTypes.STRING(64), |
||||
allowNull: false, |
||||
comment: '资源类拓展字段标识', |
||||
}, |
||||
resourceClassExpandFieldDisplayType: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: '显示类型,后期在字典添加', |
||||
defaultValue:0 |
||||
}, |
||||
resourceClassExpandFieldRelationType: { |
||||
type: DataTypes.INTEGER, |
||||
comment: '拓展字段关联类型,0字典,1资源类', |
||||
}, |
||||
resourceClassExpandFieldValue: { |
||||
type: DataTypes.STRING, |
||||
allowNull: false, |
||||
comment: '资源类拓展字段值', |
||||
}, |
||||
creator: { |
||||
type: DataTypes.STRING, |
||||
comment: '创建人', |
||||
}, |
||||
isDelete: { |
||||
type: DataTypes.STRING(64), |
||||
comment: '删除时间', |
||||
}, |
||||
}, |
||||
{ |
||||
// 这是其他模型参数
|
||||
sequelize, // 我们需要传递连接实例
|
||||
modelName: 'ResourceClassExpandField', // 我们需要选择模型名称
|
||||
// tableName: 'ResourceClassExpandField',
|
||||
comment: '资源类拓展字段表', |
||||
timestamps: true, // 不要忘记启用时间戳!
|
||||
createdAt: 'createTimestamp', // 不想要 createdAt
|
||||
// 想要 updatedAt 但是希望名称叫做 updateTimestamp
|
||||
updatedAt: 'updateTimestamp', |
||||
underscored: true, // 改成下划线格式
|
||||
}, |
||||
); |
||||
|
||||
return ResourceClassExpandField; |
||||
} |
@ -0,0 +1,61 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: graphResource2
|
||||
// | @文件描述: resourceClassRelation.dataModel.js -
|
||||
// | @创建时间: 2023-12-01 15:23
|
||||
// | @更新时间: 2023-12-01 15:23
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
import {Model} from 'sequelize'; |
||||
|
||||
export function mountResourceClassRelation(sequelize, DataTypes) { |
||||
class ResourceClassRelation extends Model { |
||||
} |
||||
|
||||
ResourceClassRelation.init({ |
||||
// 在这里定义模型属性
|
||||
resourceClassRelationId: { |
||||
type: DataTypes.INTEGER, |
||||
autoIncrement: true, |
||||
primaryKey: true, |
||||
allowNull: false, |
||||
comment: '资源类关系ID' |
||||
}, |
||||
resourceClassRelationFather: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
defaultValue: 0, |
||||
comment: '父资源类ID' |
||||
}, |
||||
resourceClassRelationTarget: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: '目标资源类ID' |
||||
}, |
||||
creator:{ |
||||
type: DataTypes.STRING, |
||||
comment: "创建人" |
||||
}, |
||||
isDelete:{ |
||||
type: DataTypes.STRING(64), |
||||
comment: "删除时间" |
||||
} |
||||
}, { |
||||
// 这是其他模型参数
|
||||
sequelize, // 我们需要传递连接实例
|
||||
modelName: 'ResourceClassRelation', // 我们需要选择模型名称
|
||||
// tableName: 'ResourceClassRelation',
|
||||
comment: "资源类关系表", |
||||
timestamps: true, // 不要忘记启用时间戳!
|
||||
createdAt: 'createTimestamp', // 不想要 createdAt
|
||||
// 想要 updatedAt 但是希望名称叫做 updateTimestamp
|
||||
updatedAt: 'updateTimestamp', |
||||
underscored: true, // 改成下划线格式
|
||||
}); |
||||
|
||||
return ResourceClassRelation |
||||
} |
@ -0,0 +1,60 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: graphResource2
|
||||
// | @文件描述: resourceEntityRelation.dataModel.js -
|
||||
// | @创建时间: 2023-12-01 15:23
|
||||
// | @更新时间: 2023-12-01 15:23
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
import {Model} from 'sequelize'; |
||||
|
||||
export function mountResourceEntityRelation(sequelize, DataTypes) { |
||||
class ResourceEntityRelation extends Model { |
||||
} |
||||
|
||||
ResourceEntityRelation.init({ |
||||
// 在这里定义模型属性
|
||||
resourceEntityRelationId: { |
||||
type: DataTypes.INTEGER, |
||||
autoIncrement: true, |
||||
primaryKey: true, |
||||
allowNull: false, |
||||
comment: '资源关系ID' |
||||
}, |
||||
resourceEntityRelationTarget: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: '目标资源ID' |
||||
}, |
||||
resourceEntityRelationFather: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: '父资源ID' |
||||
}, |
||||
creator:{ |
||||
type: DataTypes.STRING, |
||||
comment: "创建人" |
||||
}, |
||||
isDelete:{ |
||||
type: DataTypes.STRING(64), |
||||
comment: "删除时间" |
||||
} |
||||
}, { |
||||
// 这是其他模型参数
|
||||
sequelize, // 我们需要传递连接实例
|
||||
modelName: 'ResourceEntityRelation', // 我们需要选择模型名称
|
||||
// tableName: 'ResourceEntityRelation',
|
||||
comment: "资源实体关系表", |
||||
timestamps: true, // 不要忘记启用时间戳!
|
||||
createdAt: 'createTimestamp', // 不想要 createdAt
|
||||
// 想要 updatedAt 但是希望名称叫做 updateTimestamp
|
||||
updatedAt: 'updateTimestamp', |
||||
underscored: true, // 改成下划线格式
|
||||
}); |
||||
|
||||
return ResourceEntityRelation |
||||
} |
@ -0,0 +1,140 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: graphResource2
|
||||
// | @文件描述: resourceEntityStruct.dataModel.js -
|
||||
// | @创建时间: 2023-12-01 15:38
|
||||
// | @更新时间: 2023-12-01 15:38
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
import {Model} from 'sequelize'; |
||||
|
||||
export function mountResourceEntityStruct(sequelize, DataTypes) { |
||||
class ResourceEntityStruct extends Model { |
||||
} |
||||
|
||||
ResourceEntityStruct.init({ |
||||
// 在这里定义模型属性
|
||||
resourceEntityStructId: { |
||||
type: DataTypes.INTEGER, |
||||
// type: DataTypes.STRING,
|
||||
autoIncrement: true, |
||||
primaryKey: true, |
||||
allowNull: false, |
||||
comment: '元分类/模型ID' |
||||
}, |
||||
resourceClassBase: { |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: '资源类ID', |
||||
}, |
||||
resourceEntityStructName:{ |
||||
type: DataTypes.STRING, |
||||
allowNull: false, |
||||
comment: '资源实体字段名', |
||||
}, |
||||
resourceEntityStructIdentify:{ |
||||
type: DataTypes.STRING(64), |
||||
allowNull: false, |
||||
comment: '资源实体字段标识', |
||||
}, |
||||
resourceEntityStructNickname:{ |
||||
type: DataTypes.STRING, |
||||
comment: '资源实体字段别名', |
||||
}, |
||||
resourceEntityStructDescribe:{ |
||||
type: DataTypes.STRING(2048), |
||||
comment: '资源实体字段描述', |
||||
}, |
||||
resourceEntityStructRank:{ |
||||
type: DataTypes.INTEGER, |
||||
comment: '资源实体字段排序', |
||||
default:0, |
||||
}, |
||||
resourceEntityStructStorageType:{ |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
default: 0, |
||||
comment: '资源实体字段在数据库中存储的类型', |
||||
}, |
||||
resourceEntityStructStorageLength:{ |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: '资源实体字段在数据库中存储的长度', |
||||
defaultValue: 20, |
||||
}, |
||||
resourceEntityStructDisplayType:{ |
||||
type: DataTypes.INTEGER, |
||||
allowNull: false, |
||||
comment: '资源实体字段显示类型', |
||||
defaultValue: 0, |
||||
}, |
||||
resourceEntityStructIsGather:{ |
||||
type: DataTypes.BOOLEAN, |
||||
allowNull: false, |
||||
comment: '资源实体字段是否采集(0是采集、1是不采集)', |
||||
defaultValue: 0, |
||||
}, |
||||
resourceEntityStructIsRequired: { |
||||
type: DataTypes.BOOLEAN, |
||||
allowNull: true, |
||||
comment: "是否必填" |
||||
}, |
||||
resourceEntityStructAppIsShow:{ |
||||
type: DataTypes.BOOLEAN, |
||||
allowNull: false, |
||||
comment: '资源实体字段是否显示在APP上(0是显示、1是不显示)', |
||||
defaultValue: 0, |
||||
}, |
||||
resourceEntityStructWebIsShow:{ |
||||
type: DataTypes.BOOLEAN, |
||||
allowNull: false, |
||||
comment: '资源实体字段是否显示在Web上(0是显示、1是不显示)', |
||||
defaultValue: 0, |
||||
}, |
||||
resourceEntityStructAppNickname:{ |
||||
type: DataTypes.STRING, |
||||
comment: '资源实体字段APP别名', |
||||
}, |
||||
resourceEntityStructWebNickname:{ |
||||
type: DataTypes.STRING, |
||||
comment: '资源实体字段Web别名', |
||||
}, |
||||
resourceEntityStructRelationType:{ |
||||
type: DataTypes.INTEGER, |
||||
comment: '资源实体字段关联类型:0是字典、1是资源实体,但是选择资源类', |
||||
}, |
||||
resourceEntityStructRelationValue:{ |
||||
type: DataTypes.INTEGER, |
||||
comment: '资源实体字段关联值', |
||||
}, |
||||
resourceEntityStructDefaultValue:{ |
||||
type: DataTypes.STRING, |
||||
comment: "默认值" |
||||
}, |
||||
creator:{ |
||||
type: DataTypes.STRING, |
||||
comment: "创建人" |
||||
}, |
||||
isDelete:{ |
||||
type: DataTypes.STRING(64), |
||||
comment: "删除时间" |
||||
} |
||||
}, { |
||||
// 这是其他模型参数
|
||||
sequelize, // 我们需要传递连接实例
|
||||
modelName: 'ResourceEntityStruct', // 我们需要选择模型名称
|
||||
// tableName: 'ResourceEntityStruct',
|
||||
comment: "资源实体字段结构表", |
||||
timestamps: true, // 不要忘记启用时间戳!
|
||||
createdAt: 'createTimestamp', // 不想要 createdAt
|
||||
// 想要 updatedAt 但是希望名称叫做 updateTimestamp
|
||||
updatedAt: 'updateTimestamp', |
||||
underscored: true, // 改成下划线格式
|
||||
}); |
||||
|
||||
return ResourceEntityStruct |
||||
} |
@ -0,0 +1,61 @@ |
||||
// | ------------------------------------------------------------
|
||||
// | @版本: version 0.1
|
||||
// | @创建人: 【Nie-x7129】
|
||||
// | @E-mail: x71291@outlook.com
|
||||
// | @所在项目: expressgy-web-lauch
|
||||
// | @文件描述: index.js -
|
||||
// | @创建时间: 2023-11-28 22:27
|
||||
// | @更新时间: 2023-11-28 22:27
|
||||
// | @修改记录:
|
||||
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
|
||||
// | =
|
||||
// | ------------------------------------------------------------
|
||||
|
||||
import { Sequelize, DataTypes } from 'sequelize'; |
||||
|
||||
import { mountAtomModel } from '#dataModels/atomModel.dataModel.js'; |
||||
import { mountBaseDict } from '#dataModels/baseDict.dataModel.js'; |
||||
import { mountResourceClassBase } from '#dataModels/resourceClassBase.dataModel.js'; |
||||
import { mountResourceClassExpandField } from '#dataModels/resourceClassExpandField.dataModel.js'; |
||||
import {mountResourceClassRelation} from "#dataModels/resourceClassRelation.dataModel.js"; |
||||
import {mountResourceEntityStruct} from "#dataModels/resourceEntityStruct.dataModel.js"; |
||||
import {mountResourceEntityRelation} from "#dataModels/resourceEntityRelation.dataModel.js"; |
||||
|
||||
// import prodConfig from "#root/production.env.js";
|
||||
//
|
||||
// global.config = prodConfig
|
||||
|
||||
export default function createDatabase(logger) { |
||||
const { database, username, password, host, port } = |
||||
global.config.database.mysql; |
||||
// console.log(database, username, password, host, port);
|
||||
const sequelize = new Sequelize(database, username, password, { |
||||
host, |
||||
dialect: 'mysql', // 根据你的数据库类型修改
|
||||
underscored: true, |
||||
timezone: '+08:00', // 时区设置为东八区
|
||||
dialectOptions: { |
||||
dateStrings: true, // 将所有日期字段值转换成字符串格式
|
||||
typeCast: true, // 允许将字符串类型的日期字段值自动转换为 Date 类型
|
||||
}, |
||||
// 以下为一些额外配置选项
|
||||
// pool: {
|
||||
// max: 5,
|
||||
// min: 0,
|
||||
// acquire: 30000,
|
||||
// idle: 10000
|
||||
// },
|
||||
// logging: logger.debug.bind(logger),
|
||||
logging: false |
||||
}); |
||||
|
||||
mountAtomModel(sequelize, DataTypes); |
||||
mountBaseDict(sequelize, DataTypes); |
||||
mountResourceClassBase(sequelize, DataTypes); |
||||
mountResourceClassExpandField(sequelize, DataTypes); |
||||
mountResourceClassRelation(sequelize, DataTypes) |
||||
mountResourceEntityStruct(sequelize,DataTypes) |
||||
mountResourceEntityRelation(sequelize,DataTypes) |
||||
return sequelize; |
||||
} |
||||
// createDatabase()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue