├── packages
├── api
│ ├── public
│ │ └── .gitkeep
│ ├── plugins
│ │ └── .gitkeep
│ ├── funpi.js
│ ├── .npmrc
│ ├── scripts
│ │ ├── syncMysql.js
│ │ └── checkTable.js
│ ├── config
│ │ ├── custom.js
│ │ └── menu.js
│ ├── .prettierrc
│ ├── apis
│ │ └── example
│ │ │ ├── _meta.js
│ │ │ ├── delete.js
│ │ │ ├── detail.js
│ │ │ ├── update.js
│ │ │ ├── insert.js
│ │ │ ├── select.js
│ │ │ └── _ffffff.js
│ ├── tables
│ │ └── example.js
│ ├── addons
│ │ └── weixin
│ │ │ ├── tables
│ │ │ └── pay.js
│ │ │ └── apis
│ │ │ └── pay
│ │ │ ├── _meta.js
│ │ │ └── select.js
│ ├── README.md
│ ├── pm2.config.cjs
│ ├── .env.production
│ ├── .env.development
│ ├── .gitignore
│ └── package.json
├── admin
│ ├── src
│ │ ├── utils
│ │ │ ├── index.js
│ │ │ └── internal.js
│ │ ├── components
│ │ │ └── .gitkeep
│ │ ├── pages
│ │ │ ├── index
│ │ │ │ ├── route.js
│ │ │ │ └── index.vue
│ │ │ └── internal
│ │ │ │ ├── readme.md
│ │ │ │ ├── admin
│ │ │ │ ├── route.js
│ │ │ │ └── components
│ │ │ │ │ └── editDataDrawer.vue
│ │ │ │ ├── api
│ │ │ │ ├── route.js
│ │ │ │ └── index.vue
│ │ │ │ ├── dict
│ │ │ │ ├── route.js
│ │ │ │ └── components
│ │ │ │ │ └── editDataDrawer.vue
│ │ │ │ ├── mailLog
│ │ │ │ ├── route.js
│ │ │ │ ├── components
│ │ │ │ │ └── sendMailDrawer.vue
│ │ │ │ └── index.vue
│ │ │ │ ├── menu
│ │ │ │ ├── route.js
│ │ │ │ └── index.vue
│ │ │ │ ├── role
│ │ │ │ └── route.js
│ │ │ │ ├── appConfig
│ │ │ │ ├── route.js
│ │ │ │ └── index.vue
│ │ │ │ ├── dictCategory
│ │ │ │ ├── route.js
│ │ │ │ ├── components
│ │ │ │ │ └── editDataDrawer.vue
│ │ │ │ └── index.vue
│ │ │ │ ├── adminActionLog
│ │ │ │ ├── route.js
│ │ │ │ └── index.vue
│ │ │ │ ├── adminLoginLog
│ │ │ │ ├── route.js
│ │ │ │ └── index.vue
│ │ │ │ └── login
│ │ │ │ └── route.js
│ │ ├── i18n
│ │ │ ├── en.json
│ │ │ └── zh.json
│ │ ├── styles
│ │ │ ├── global.scss
│ │ │ ├── variable.scss
│ │ │ └── internal.scss
│ │ ├── config
│ │ │ ├── app.js
│ │ │ └── internal.js
│ │ ├── assets
│ │ │ ├── logo.png
│ │ │ ├── login-back.png
│ │ │ └── login-left-image.png
│ │ ├── plugins
│ │ │ ├── storage.js
│ │ │ ├── router.js
│ │ │ ├── i18n.js
│ │ │ ├── global.js
│ │ │ └── http.js
│ │ ├── main.js
│ │ ├── App.vue
│ │ └── layouts
│ │ │ └── default
│ │ │ └── components
│ │ │ └── sideMenu.vue
│ ├── .npmrc
│ ├── .env.development
│ ├── .env.production
│ ├── public
│ │ └── logo.png
│ ├── .prettierrc
│ ├── jsconfig.json
│ ├── README.md
│ ├── index.html
│ ├── .gitignore
│ ├── yite.config.js
│ └── package.json
├── funpi
│ ├── config
│ │ ├── path.js
│ │ ├── role.js
│ │ ├── http.js
│ │ ├── menu.js
│ │ └── env.js
│ ├── apis
│ │ ├── tool
│ │ │ ├── _meta.js
│ │ │ ├── tokenCheck.js
│ │ │ └── sendMail.js
│ │ ├── dict
│ │ │ ├── _meta.js
│ │ │ ├── categorySelectAll.js
│ │ │ ├── dictDelete.js
│ │ │ ├── dictSelectAll.js
│ │ │ ├── categorySelectPage.js
│ │ │ ├── categoryDelete.js
│ │ │ ├── categoryInsert.js
│ │ │ ├── categoryUpdate.js
│ │ │ ├── dictSelectPage.js
│ │ │ ├── dictInsert.js
│ │ │ └── dictUpdate.js
│ │ └── admin
│ │ │ ├── getApis.js
│ │ │ ├── getMenus.js
│ │ │ ├── apiSelectAll.js
│ │ │ ├── menuSelectAll.js
│ │ │ ├── _meta.js
│ │ │ ├── roleDetail.js
│ │ │ ├── roleSelectAll.js
│ │ │ ├── adminDelete.js
│ │ │ ├── apiSelectPage.js
│ │ │ ├── menuSelectPage.js
│ │ │ ├── adminLoginLogSelectPage.js
│ │ │ ├── adminActionLogSelectPage.js
│ │ │ ├── adminSelectPage.js
│ │ │ ├── roleDelete.js
│ │ │ ├── mailSelectPage.js
│ │ │ ├── roleSelectPage.js
│ │ │ ├── adminInsert.js
│ │ │ ├── roleInsert.js
│ │ │ ├── adminUpdate.js
│ │ │ ├── roleUpdate.js
│ │ │ └── adminLogin.js
│ ├── bootstrap
│ │ ├── upload.js
│ │ ├── redis.js
│ │ ├── cors.js
│ │ ├── mail.js
│ │ ├── xmlParse.js
│ │ ├── auth.js
│ │ └── tool.js
│ ├── plugins
│ │ ├── jwt.js
│ │ ├── swagger.js
│ │ └── logger.js
│ ├── tables
│ │ ├── dictCategory.js
│ │ ├── adminLoginLog.js
│ │ ├── api.js
│ │ ├── role.js
│ │ ├── mailLog.js
│ │ ├── adminActionLog.js
│ │ ├── menu.js
│ │ ├── dict.js
│ │ └── admin.js
│ ├── todo.md
│ ├── schema
│ │ └── menu.js
│ ├── package.json
│ ├── utils
│ │ ├── colors.js
│ │ └── check.js
│ └── README.md
├── cli
│ ├── README.md
│ └── package.json
└── vscode
│ ├── README.md
│ └── package.json
├── .prettierignore
├── .npmrc
├── .prettierrc
├── lerna.json
├── package.json
├── .gitignore
└── README.md
/packages/api/public/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/admin/src/utils/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/api/plugins/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | LICENSE
2 | LICENSE.md
--------------------------------------------------------------------------------
/packages/admin/src/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/index/route.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/readme.md:
--------------------------------------------------------------------------------
1 | 此目录下的文件不要改动!!!
2 |
--------------------------------------------------------------------------------
/packages/admin/src/i18n/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/admin/src/i18n/zh.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "测试"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/admin/route.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/api/route.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/dict/route.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/mailLog/route.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/menu/route.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/role/route.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/packages/admin/src/styles/global.scss:
--------------------------------------------------------------------------------
1 | @use './internal.scss' as *;
2 |
--------------------------------------------------------------------------------
/packages/admin/src/styles/variable.scss:
--------------------------------------------------------------------------------
1 | $layout-header-height: 54px;
2 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/appConfig/route.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/dictCategory/route.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/adminActionLog/route.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/adminLoginLog/route.js:
--------------------------------------------------------------------------------
1 | export default {};
2 |
--------------------------------------------------------------------------------
/packages/api/funpi.js:
--------------------------------------------------------------------------------
1 | import { initServer } from 'funpi';
2 | initServer();
3 |
--------------------------------------------------------------------------------
/packages/admin/src/config/app.js:
--------------------------------------------------------------------------------
1 | export const $AppConfig = {
2 | appName: '易管理'
3 | };
4 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/login/route.js:
--------------------------------------------------------------------------------
1 | export default {
2 | layout: false
3 | };
4 |
--------------------------------------------------------------------------------
/packages/admin/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmmirror.com
2 | # registry=https://registry.npmjs.org
--------------------------------------------------------------------------------
/packages/api/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmmirror.com
2 | # registry=https://registry.npmjs.org
--------------------------------------------------------------------------------
/packages/admin/.env.development:
--------------------------------------------------------------------------------
1 | VITE_HOST="http://127.0.0.1:3000/api"
2 | VITE_NAMESPACE="dev.funpi-admin"
--------------------------------------------------------------------------------
/packages/api/scripts/syncMysql.js:
--------------------------------------------------------------------------------
1 | import { syncMysql } from 'funpi/scripts/syncMysql';
2 | syncMysql();
3 |
--------------------------------------------------------------------------------
/packages/admin/.env.production:
--------------------------------------------------------------------------------
1 | VITE_HOST="https://funpi-api.yicode.tech/api"
2 | VITE_NAMESPACE="prod.funpi-admin"
--------------------------------------------------------------------------------
/packages/admin/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenbimo/funpi/HEAD/packages/admin/public/logo.png
--------------------------------------------------------------------------------
/packages/api/scripts/checkTable.js:
--------------------------------------------------------------------------------
1 | import { checkTable } from 'funpi/scripts/checkTable';
2 | checkTable();
3 |
--------------------------------------------------------------------------------
/packages/admin/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenbimo/funpi/HEAD/packages/admin/src/assets/logo.png
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmmirror.com
2 | # registry=https://registry.npmjs.org
3 | link-workspace-packages=true
--------------------------------------------------------------------------------
/packages/admin/src/assets/login-back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenbimo/funpi/HEAD/packages/admin/src/assets/login-back.png
--------------------------------------------------------------------------------
/packages/admin/src/assets/login-left-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenbimo/funpi/HEAD/packages/admin/src/assets/login-left-image.png
--------------------------------------------------------------------------------
/packages/admin/src/plugins/storage.js:
--------------------------------------------------------------------------------
1 | import store2 from 'store2';
2 | const $Storage = store2.namespace(import.meta.env.VITE_NAMESPACE);
3 |
4 | // 提供给手动导入使用
5 | export { $Storage };
6 |
--------------------------------------------------------------------------------
/packages/funpi/config/path.js:
--------------------------------------------------------------------------------
1 | import { dirname, resolve, normalize } from 'pathe';
2 | export const appDir = normalize(process.cwd());
3 | export const funpiDir = dirname(dirname(import.meta.filename));
4 |
--------------------------------------------------------------------------------
/packages/api/config/custom.js:
--------------------------------------------------------------------------------
1 | // 自定义配置
2 | const customConfig = {
3 | tencentCloud: {
4 | secretId: '',
5 | secretKey: '',
6 | bucket: '',
7 | region: ''
8 | }
9 | };
10 |
11 | export { customConfig };
12 |
--------------------------------------------------------------------------------
/packages/admin/src/plugins/router.js:
--------------------------------------------------------------------------------
1 | import { yiteRoutes } from 'virtual:yite-router';
2 |
3 | // 创建路由
4 | const $Router = createRouter({
5 | routes: yiteRoutes(),
6 | history: createWebHashHistory()
7 | });
8 |
9 | export { $Router };
10 |
--------------------------------------------------------------------------------
/packages/funpi/apis/tool/_meta.js:
--------------------------------------------------------------------------------
1 | export const metaConfig = {
2 | dirName: '工具',
3 | apiNames: {
4 | sendMail: '发送邮件',
5 | tokenCheck: '令牌检测',
6 | getOnline: '获取在线统计',
7 | setOnline: '设置在线统计'
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/packages/api/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "tabWidth": 4,
4 | "semi": true,
5 | "singleQuote": true,
6 | "printWidth": 1024,
7 | "bracketSpacing": true,
8 | "useTabs": false,
9 | "arrowParens": "always"
10 | }
11 |
--------------------------------------------------------------------------------
/packages/admin/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "tabWidth": 4,
4 | "semi": true,
5 | "singleQuote": true,
6 | "printWidth": 1024,
7 | "bracketSpacing": true,
8 | "useTabs": false,
9 | "arrowParens": "always"
10 | }
11 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "tabWidth": 4,
4 | "semi": true,
5 | "singleQuote": true,
6 | "printWidth": 1024,
7 | "bracketSpacing": true,
8 | "useTabs": false,
9 | "arrowParens": "always",
10 | "endOfLine": "lf"
11 | }
12 |
--------------------------------------------------------------------------------
/packages/admin/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "baseUrl": "./",
6 | "paths": {
7 | "@/*": ["src/*"]
8 | }
9 | },
10 | "exclude": ["node_modules", "dist"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/admin/src/plugins/i18n.js:
--------------------------------------------------------------------------------
1 | import { createI18n } from 'vue-i18n';
2 | import { yiteMessages } from 'virtual:yite-messages';
3 |
4 | const $I18n = createI18n({
5 | legacy: false,
6 | locale: 'zh',
7 | messages: await yiteMessages()
8 | });
9 |
10 | export { $I18n };
11 |
--------------------------------------------------------------------------------
/packages/admin/src/main.js:
--------------------------------------------------------------------------------
1 | import App from '@/App.vue';
2 | import '@arco-design/web-vue/dist/arco.css';
3 | import 'virtual:uno.css';
4 |
5 | const $App = createApp(App);
6 | const $Pinia = createPinia();
7 |
8 | $App.use($Router);
9 | $App.use($Pinia);
10 | $App.use($I18n);
11 |
12 | $App.mount('#app');
13 |
--------------------------------------------------------------------------------
/packages/api/apis/example/_meta.js:
--------------------------------------------------------------------------------
1 | export const metaConfig = {
2 | // 目录名称
3 | dirName: '演示表',
4 | // 接口名称
5 | apiNames: {
6 | insert: '添加功能演示',
7 | delete: '删除功能演示',
8 | select: '分页功能演示',
9 | update: '更新功能演示',
10 | detail: '详情功能演示'
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/packages/api/tables/example.js:
--------------------------------------------------------------------------------
1 | export const tableName = '新闻示例表';
2 | export const tableData = {
3 | title: {
4 | name: '新闻标题',
5 | type: 'mediumText',
6 | min: 1,
7 | max: 50
8 | },
9 | content: {
10 | name: '新闻内容',
11 | type: 'mediumText',
12 | min: 0
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/packages/api/config/menu.js:
--------------------------------------------------------------------------------
1 | export const menuConfig = [
2 | // 以下为菜单格式示例
3 | // {
4 | // path: '/banner',
5 | // name: '轮播管理',
6 | // children: [
7 | // {
8 | // path: '/banner/lists',
9 | // name: '轮播列表'
10 | // }
11 | // ]
12 | // }
13 | ];
14 |
--------------------------------------------------------------------------------
/packages/api/addons/weixin/tables/pay.js:
--------------------------------------------------------------------------------
1 | export const tableName = '微信插件表';
2 | export const tableData = {
3 | title: {
4 | name: '新闻标题',
5 | type: 'mediumText',
6 | min: 1,
7 | max: 50
8 | },
9 | content: {
10 | name: '新闻内容',
11 | type: 'mediumText',
12 | min: 0
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/packages/admin/README.md:
--------------------------------------------------------------------------------
1 | # funpi 是什么?
2 |
3 | 中文名称 `放屁` 接口框架。
4 |
5 | 像放屁一样简单又自然的 `Node.js` 接口开发框架。
6 |
7 | > 注意:本项目 `v6` 版本为自用,`v7` 为内测版,`v8` 才是公测版。
8 |
9 | ### 仓库地址
10 |
11 | [github - https://github.com/chenbimo/funpi](https://github.com/chenbimo/funpi)
12 |
13 | ### 作者介绍
14 |
15 | [前端之虎陈随易 https://chensuiyi.me](https://chensuiyi.me)
16 |
17 | ### 文档教程
18 |
19 | 请到 [前端之虎陈随易 https://chensuiyi.me](https://chensuiyi.me) 网站查看。
20 |
--------------------------------------------------------------------------------
/packages/api/README.md:
--------------------------------------------------------------------------------
1 | # funpi 是什么?
2 |
3 | 中文名称 `放屁` 接口框架。
4 |
5 | 像放屁一样简单又自然的 `Node.js` 接口开发框架。
6 |
7 | > 注意:本项目 `v6` 版本为自用,`v7` 为内测版,`v8` 才是公测版。
8 |
9 | ### 仓库地址
10 |
11 | [github - https://github.com/chenbimo/funpi](https://github.com/chenbimo/funpi)
12 |
13 | ### 作者介绍
14 |
15 | [前端之虎陈随易 https://chensuiyi.me](https://chensuiyi.me)
16 |
17 | ### 文档教程
18 |
19 | 请到 [前端之虎陈随易 https://chensuiyi.me](https://chensuiyi.me) 网站查看。
20 |
--------------------------------------------------------------------------------
/packages/cli/README.md:
--------------------------------------------------------------------------------
1 | # funpi 是什么?
2 |
3 | 中文名称 `放屁` 接口框架。
4 |
5 | 像放屁一样简单又自然的 `Node.js` 接口开发框架。
6 |
7 | > 注意:本项目 `v6` 版本为自用,`v7` 为内测版,`v8` 才是公测版。
8 |
9 | ### 仓库地址
10 |
11 | [github - https://github.com/chenbimo/funpi](https://github.com/chenbimo/funpi)
12 |
13 | ### 作者介绍
14 |
15 | [前端之虎陈随易 https://chensuiyi.me](https://chensuiyi.me)
16 |
17 | ### 文档教程
18 |
19 | 请到 [前端之虎陈随易 https://chensuiyi.me](https://chensuiyi.me) 网站查看。
20 |
--------------------------------------------------------------------------------
/packages/vscode/README.md:
--------------------------------------------------------------------------------
1 | # funpi 是什么?
2 |
3 | 中文名称 `放屁` 接口框架。
4 |
5 | 像放屁一样简单又自然的 `Node.js` 接口开发框架。
6 |
7 | > 注意:本项目 `v6` 版本为自用,`v7` 为内测版,`v8` 才是公测版。
8 |
9 | ### 仓库地址
10 |
11 | [github - https://github.com/chenbimo/funpi](https://github.com/chenbimo/funpi)
12 |
13 | ### 作者介绍
14 |
15 | [前端之虎陈随易 https://chensuiyi.me](https://chensuiyi.me)
16 |
17 | ### 文档教程
18 |
19 | 请到 [前端之虎陈随易 https://chensuiyi.me](https://chensuiyi.me) 网站查看。
20 |
--------------------------------------------------------------------------------
/packages/admin/src/plugins/global.js:
--------------------------------------------------------------------------------
1 | export const useGlobal = defineStore('global', () => {
2 | // 全局数据
3 | const $GlobalData = $ref({
4 | // 内置配置,不要修改
5 | ...$InternalConfig
6 | });
7 |
8 | // 全局计算数据
9 | const $GlobalComputed = {};
10 |
11 | // 全局方法
12 | const $GlobalMethod = {};
13 |
14 | return {
15 | $GlobalData,
16 | $GlobalComputed,
17 | $GlobalMethod
18 | };
19 | });
20 |
--------------------------------------------------------------------------------
/packages/funpi/apis/dict/_meta.js:
--------------------------------------------------------------------------------
1 | export const metaConfig = {
2 | dirName: '字典管理',
3 | apiNames: {
4 | categoryDelete: '删除字典分类',
5 | categoryInsert: '添加字典分类',
6 | categorySelectAll: '查询字典分类-所有',
7 | categorySelectPage: '查询字典分类-分页',
8 | categoryUpdate: '更新字典分类',
9 | dictDelete: '删除字典',
10 | dictInsert: '添加字典',
11 | dictSelectAll: '查询字典-所有',
12 | dictSelectPage: '查询字典-分页',
13 | dictUpdate: '更新字典'
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/packages/admin/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
20 |
23 |
--------------------------------------------------------------------------------
/packages/funpi/config/role.js:
--------------------------------------------------------------------------------
1 | export const roleConfig = {
2 | visitor: {
3 | name: '游客',
4 | sort: 4,
5 | describe: '具备有限的权限和有限的查看内容'
6 | },
7 | user: {
8 | name: '用户',
9 | sort: 3,
10 | describe: '用户权限和对于的内容查看'
11 | },
12 | admin: {
13 | name: '管理',
14 | sort: 2,
15 | describe: '管理权限、除开发相关权限之外的权限等'
16 | },
17 | super: {
18 | name: '超级管理',
19 | sort: 1,
20 | describe: '超级管理权限、除开发相关权限之外的权限等'
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "7.20.27",
3 | "packages": [
4 | "packages/*"
5 | ],
6 | "npmClient": "bun",
7 | "command": {
8 | "version": {
9 | "allowBranch": [
10 | "main",
11 | "master"
12 | ],
13 | "changelog": false,
14 | "gitTagVersion": true,
15 | "yes": true,
16 | "forcePublish": true,
17 | "syncWorkspaceLock": true,
18 | "exact": true,
19 | "push": false
20 | },
21 | "publish": {
22 | "ignoreScripts": false,
23 | "yes": true
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/packages/admin/src/config/internal.js:
--------------------------------------------------------------------------------
1 | export const $InternalConfig = {
2 | // 用户令牌
3 | token: $Storage.local.get('token') || '',
4 | // 用户数据
5 | userData: $Storage.local.get('userData') || {},
6 | // 表格边框
7 | tableBordered: {
8 | cell: true
9 | },
10 | // 表格滚动
11 | tableScroll: {
12 | x: '100%',
13 | y: '100%',
14 | maxHeight: '100%'
15 | },
16 | modalShortWidth: 600,
17 | modalLongWidth: 1200,
18 | // 抽屉默认宽度
19 | drawerWidth: 400,
20 | // 每页显示数量
21 | pageLimit: 30
22 | };
23 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/index/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
27 |
28 |
32 |
--------------------------------------------------------------------------------
/packages/admin/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 易管理
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/packages/funpi/bootstrap/upload.js:
--------------------------------------------------------------------------------
1 | import { fp } from 'funpi';
2 | import fastifyMultipart from '@fastify/multipart';
3 |
4 | async function plugin(fastify) {
5 | await fastify.register(fastifyMultipart, {
6 | attachFieldsToBody: true,
7 | limits: {
8 | fieldNameSize: 100,
9 | fieldSize: 100,
10 | fields: 10,
11 | fileSize: 100000000,
12 | files: 1,
13 | headerPairs: 2000,
14 | parts: 1000
15 | }
16 | });
17 | }
18 |
19 | export default fp(plugin, {
20 | name: 'upload'
21 | });
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "funpi",
3 | "version": "1.0.0",
4 | "description": "放屁接口 + 后台管理",
5 | "main": "funpi.js",
6 | "type": "module",
7 | "publishConfig": {
8 | "access": "restricted"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/chenbimo/funpi.git"
13 | },
14 | "scripts": {
15 | "r": "lerna publish"
16 | },
17 | "author": "chensuiyi ",
18 | "homepage": "https://chensuiyi.me",
19 | "workspaces": [
20 | "packages/*"
21 | ],
22 | "devDependencies": {
23 | "lerna": "^8.2.2"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/funpi/plugins/jwt.js:
--------------------------------------------------------------------------------
1 | // 外部模块
2 | import fp from 'fastify-plugin';
3 | import fastifyJwt from '@fastify/jwt';
4 |
5 | async function plugin(fastify) {
6 | await fastify.register(fastifyJwt, {
7 | secret: process.env.JWT_SECRET,
8 | decoratorName: 'session',
9 | decode: {
10 | complete: true
11 | },
12 | sign: {
13 | algorithm: process.env.JWT_ALGORITHM,
14 | expiresIn: process.env.JWT_EXPIRES_IN
15 | },
16 | verify: {
17 | algorithms: [process.env.JWT_ALGORITHM]
18 | }
19 | });
20 | }
21 |
22 | export default fp(plugin, { name: 'jwt' });
23 |
--------------------------------------------------------------------------------
/packages/funpi/tables/dictCategory.js:
--------------------------------------------------------------------------------
1 | export const tableName = '字典分类表';
2 | export const tableData = {
3 | code: {
4 | name: '字典分类编码',
5 | type: 'string',
6 | default: '',
7 | min: 1,
8 | max: 50,
9 | pattern: '^[a-z][a-z0-9_-]*$'
10 | },
11 | name: {
12 | name: '字典分类名称',
13 | type: 'string',
14 | default: '',
15 | min: 1,
16 | max: 100
17 | },
18 | sort: {
19 | name: '字典分类排序',
20 | type: 'bigInt',
21 | default: 0,
22 | min: 0
23 | },
24 | describe: {
25 | name: '描述',
26 | type: 'string',
27 | default: '',
28 | min: 0,
29 | max: 500
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/packages/api/pm2.config.cjs:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const dotenv = require('dotenv');
3 | const envConfig = dotenv.parse(fs.readFileSync('./.env.production'));
4 |
5 | module.exports = {
6 | apps: [
7 | {
8 | name: 'funpi',
9 | instances: 1,
10 | script: './funpi.js',
11 | exec_mode: 'cluster',
12 | watch: false,
13 | autorestart: true,
14 | interpreter: 'bun',
15 | ignore_watch: ['node_modules', 'logs', 'data'],
16 | env: envConfig,
17 | log_file: './logs/funpi.log',
18 | error_file: './logs/funpi-error.log',
19 | out_file: './logs/funpi-out.log',
20 | max_memory_restart: '200M'
21 | }
22 | ]
23 | };
24 |
--------------------------------------------------------------------------------
/packages/funpi/bootstrap/redis.js:
--------------------------------------------------------------------------------
1 | import fp from 'fastify-plugin';
2 | import { createClient } from 'redis';
3 |
4 | async function plugin(fastify, opts) {
5 | if (fastify.redis) {
6 | return;
7 | }
8 | const client = await createClient({
9 | username: process.env.REDIS_USERNAME,
10 | password: process.env.REDIS_PASSWORD,
11 | database: Number(process.env.REDIS_DB),
12 | socket: {
13 | reconnectStrategy: (retries) => {
14 | return false;
15 | }
16 | }
17 | })
18 | .on('error', (err) => {
19 | fastify.log.error({ msg: 'Redis连接失败', detail: err });
20 | process.exit(1);
21 | })
22 | .connect();
23 |
24 | fastify.decorate('redis', client);
25 | }
26 | export default fp(plugin, { name: 'funpiRedis' });
27 |
--------------------------------------------------------------------------------
/packages/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@funpi/cli",
3 | "version": "7.20.27",
4 | "description": "FunPi(放屁) - 命令行占位",
5 | "type": "module",
6 | "private": false,
7 | "publishConfig": {
8 | "access": "public",
9 | "registry": "https://registry.npmjs.org"
10 | },
11 | "exports": {},
12 | "keywords": [
13 | "fastify",
14 | "nodejs",
15 | "api"
16 | ],
17 | "files": [
18 | "LICENSE",
19 | "package.json",
20 | "README.md"
21 | ],
22 | "engines": {
23 | "node": ">=20.6.0"
24 | },
25 | "author": "chensuiyi ",
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/chenbimo/funpi"
29 | },
30 | "homepage": "https://chensuiyi.me",
31 | "gitHead": "1c28c0de7c0af8aa4582c45ab2d98e66c597c7a1"
32 | }
33 |
--------------------------------------------------------------------------------
/packages/vscode/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@funpi/vscode",
3 | "version": "7.20.27",
4 | "description": "FunPi(放屁) - VSCode插件占位",
5 | "type": "module",
6 | "private": false,
7 | "publishConfig": {
8 | "access": "public",
9 | "registry": "https://registry.npmjs.org"
10 | },
11 | "exports": {},
12 | "keywords": [
13 | "fastify",
14 | "nodejs",
15 | "api"
16 | ],
17 | "files": [
18 | "LICENSE",
19 | "package.json",
20 | "README.md"
21 | ],
22 | "engines": {
23 | "node": ">=20.6.0"
24 | },
25 | "author": "chensuiyi ",
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/chenbimo/funpi"
29 | },
30 | "homepage": "https://chensuiyi.me",
31 | "gitHead": "1c28c0de7c0af8aa4582c45ab2d98e66c597c7a1"
32 | }
33 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/getApis.js:
--------------------------------------------------------------------------------
1 | import { fnRoute } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {}
10 | },
11 | // 执行函数
12 | apiHandler: async (req) => {
13 | try {
14 | const result = await fastify.getUserApis(req.session);
15 | return {
16 | ...httpConfig.SELECT_SUCCESS,
17 | data: {
18 | rows: result
19 | }
20 | };
21 | } catch (err) {
22 | fastify.log.error(err);
23 | return httpConfig.SELECT_FAIL;
24 | }
25 | }
26 | });
27 | };
28 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/getMenus.js:
--------------------------------------------------------------------------------
1 | import { fnRoute } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {}
10 | },
11 | // 执行函数
12 | apiHandler: async (req) => {
13 | try {
14 | const result = await fastify.getUserMenus(req.session);
15 | return {
16 | ...httpConfig.SELECT_SUCCESS,
17 | data: {
18 | rows: result
19 | }
20 | };
21 | } catch (err) {
22 | fastify.log.error(err);
23 | return httpConfig.SELECT_FAIL;
24 | }
25 | }
26 | });
27 | };
28 |
--------------------------------------------------------------------------------
/packages/funpi/todo.md:
--------------------------------------------------------------------------------
1 | ## 待办
2 |
3 | - [ ] 限制图片上传大小
4 | - [ ] 管理上传的图片
5 | - [ ] 同步数据库结构的时候,判断为必须停服维护才可以
6 | - [ ] 处理异步通知问题,可能需要用到消息队列
7 | - [ ] 添加和更新菜单后,管理员可以可以立马看到效果
8 | - [ ] 图片作为日志打印了
9 | - [ ] 图片上传buffer警告问题,写法更新
10 | - [x] 没有改动的系统表不要更新
11 | - [ ] fnInreUUID函数优化为更稳定的版本
12 | - [x] 把syncDatabase函数从内核移出
13 | - [x] 局域网访问
14 | - [ ] 定时还原数据库功能
15 | - [ ] 单点登录
16 | - [ ] jwt 放到redis中,可以被主动踢出功能
17 | - [ ] 拉黑功能
18 | - [ ] 初始化数据库自增模式后,禁止再次改变
19 | - [ ] 支付订单支持优惠码(直接减免),折扣码(百分比)减免
20 | - [x] 系统接口和用户接口重复的判断
21 | - [ ] 增加对控制公众号相关的功能
22 | - [ ] 增加对跨域的配置支持
23 | - [ ] 增加一个配置可视化面板
24 | - [ ] 给接口做隐藏验证,如果不是官方域名的请求,则不予返回数据。
25 | - [ ] 增加监控报错接口和功能
26 | - [ ] 限速要排除微信回调接口
27 | - [ ] 实现在线人数等功能
28 | - [ ] 数据库同步字段名称改变不要删除,把该字段改成其他名称。
29 | - [ ] 提供数据库创建和对比,不提供同步功能。
30 | - [ ] 限制Node.js版本只能为v20
31 | - [ ] 判断process.env.NODE_ENV是否为production或development
32 | - [x] 判断系统接口和用户接口名称冲突问题
33 | - [ ] 缓存使用配置文件统一管理
34 | - [ ] 每个接口可以限制请求来源,IP等
35 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/apiSelectAll.js:
--------------------------------------------------------------------------------
1 | import { fnRoute } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {}
10 | },
11 | // 执行函数
12 | apiHandler: async () => {
13 | try {
14 | const result = await fastify.redisGet('cacheData:api');
15 |
16 | return {
17 | ...httpConfig.SELECT_SUCCESS,
18 | data: {
19 | rows: result
20 | }
21 | };
22 | } catch (err) {
23 | fastify.log.error(err);
24 | return httpConfig.SELECT_FAIL;
25 | }
26 | }
27 | });
28 | };
29 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/menuSelectAll.js:
--------------------------------------------------------------------------------
1 | import { fnRoute } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {}
10 | },
11 | // 执行函数
12 | apiHandler: async () => {
13 | try {
14 | const result = await fastify.redisGet('cacheData:menu');
15 |
16 | return {
17 | ...httpConfig.SELECT_SUCCESS,
18 | data: {
19 | rows: result
20 | }
21 | };
22 | } catch (err) {
23 | fastify.log.error(err);
24 | return httpConfig.SELECT_FAIL;
25 | }
26 | }
27 | });
28 | };
29 |
--------------------------------------------------------------------------------
/packages/funpi/tables/adminLoginLog.js:
--------------------------------------------------------------------------------
1 | export const tableName = '管理员登录日志表';
2 | export const tableData = {
3 | user_id: {
4 | name: '登录用户ID',
5 | type: 'bigInt',
6 | default: 0,
7 | isIndex: true,
8 | min: 0
9 | },
10 | username: {
11 | name: '用户名',
12 | type: 'string',
13 | default: '',
14 | max: 50
15 | },
16 | nickname: {
17 | name: '昵称',
18 | type: 'string',
19 | default: '',
20 | max: 50
21 | },
22 | role: {
23 | name: '角色',
24 | type: 'string',
25 | default: '',
26 | max: 50
27 | },
28 | ip: {
29 | name: 'ip',
30 | type: 'string',
31 | default: '',
32 | max: 50
33 | },
34 | ua: {
35 | name: 'ua',
36 | type: 'string',
37 | default: '',
38 | max: 500
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/packages/api/.env.production:
--------------------------------------------------------------------------------
1 | NODE_ENV="production"
2 | # 项目名称
3 | APP_NAME="易接口"
4 | APP_PORT=3000
5 | # MD5加密盐
6 | MD5_SALT="funpi123456"
7 | # 监听端口
8 | LISTEN_HOST="127.0.0.1"
9 | # 开发管理员密码
10 | DEV_PASSWORD="funpi123456"
11 | # 请求体大小
12 | BODY_LIMIT=10
13 | # 参数检查
14 | PARAMS_CHECK=0
15 | # 日志等级
16 | LOG_LEVEL="warn"
17 | # 时区
18 | TIMEZONE="Asia/Shanghai"
19 | # mysql 配置
20 | MYSQL_HOST="127.0.0.1"
21 | MYSQL_PORT=3306
22 | MYSQL_DB="funpi_demo"
23 | MYSQL_USERNAME="root"
24 | MYSQL_PASSWORD="root"
25 | TABLE_PRIMARY_KEY="default"
26 | # redis 配置
27 | REDIS_HOST="127.0.0.1"
28 | REDIS_PORT=6379
29 | REDIS_USERNAME=""
30 | REDIS_PASSWORD=""
31 | REDIS_DB=0
32 | REDIS_KEY_PREFIX="funpi_demo"
33 | # JWT 配置
34 | JWT_SECRET="funpi123456"
35 | JWT_EXPIRES_IN="30d"
36 | JWT_ALGORITHM="HS256"
37 | # 邮箱配置
38 | MAIL_HOST='demo.com'
39 | MAIL_PORT=465
40 | MAIL_POOL=1
41 | MAIL_SECURE=1
42 | MAIL_USER='demo@qq.com'
43 | MAIL_PASS=''
44 | MAIL_SENDER='易接口'
45 | MAIL_ADDRESS='demo@qq.com'
46 |
--------------------------------------------------------------------------------
/packages/api/addons/weixin/apis/pay/_meta.js:
--------------------------------------------------------------------------------
1 | export const metaConfig = {
2 | // 目录名称
3 | dirName: '管理员',
4 | // 接口名称
5 | apiNames: {
6 | select: '查询管理员接口权限',
7 | getMenus: '查询管理员菜单权限',
8 | adminLogin: '管理员登录',
9 | adminLoginLogSelectPage: '管理员登录日志-分页',
10 | adminActionLogSelectPage: '管理员操作日志-分页',
11 | adminInsert: '添加管理员',
12 | adminDelete: '删除管理员',
13 | adminSelectPage: '查询管理员-分页',
14 | adminUpdate: '更新管理员',
15 | apiSelectAll: '查询接口-全部',
16 | apiSelectPage: '查询接口-分页',
17 | menuDelete: '删除菜单',
18 | menuSelectAll: '查询菜单-全部',
19 | menuSelectPage: '查询菜单-分页',
20 | menuUpdate: '更新菜单',
21 | menuInsert: '添加菜单',
22 | roleDelete: '删除角色',
23 | roleInsert: '添加角色',
24 | roleSelectAll: '查询角色-全部',
25 | roleSelectPage: '查询角色-分页',
26 | roleUpdate: '更新角色',
27 | mailSelectPage: '查询邮件日志-分页'
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/packages/api/.env.development:
--------------------------------------------------------------------------------
1 | NODE_ENV="development"
2 | # 项目名称
3 | APP_NAME="易接口"
4 | APP_PORT=3000
5 | # MD5加密盐
6 | MD5_SALT="funpi123456"
7 | # 监听端口
8 | LISTEN_HOST="127.0.0.1"
9 | # 开发管理员密码
10 | DEV_PASSWORD="funpi123456"
11 | # 请求体大小
12 | BODY_LIMIT=10
13 | # 参数检查
14 | PARAMS_CHECK=0
15 | # 日志等级
16 | LOG_LEVEL="warn"
17 | # 时区
18 | TIMEZONE="Asia/Shanghai"
19 | # mysql 配置
20 | MYSQL_HOST="127.0.0.1"
21 | MYSQL_PORT=3306
22 | MYSQL_DB="funpi_demo"
23 | MYSQL_USERNAME="root"
24 | MYSQL_PASSWORD="root"
25 | TABLE_PRIMARY_KEY="default"
26 | # redis 配置
27 | REDIS_HOST="127.0.0.1"
28 | REDIS_PORT=6379
29 | REDIS_USERNAME=""
30 | REDIS_PASSWORD=""
31 | REDIS_DB=2
32 | REDIS_KEY_PREFIX="funpi_demo"
33 | # JWT 配置
34 | JWT_SECRET="funpi123456"
35 | JWT_EXPIRES_IN="30d"
36 | JWT_ALGORITHM="HS256"
37 | # 邮箱配置
38 | MAIL_HOST='demo.com'
39 | MAIL_PORT=465
40 | MAIL_POOL=1
41 | MAIL_SECURE=1
42 | MAIL_USER='demo@qq.com'
43 | MAIL_PASS=''
44 | MAIL_SENDER='易接口'
45 | MAIL_ADDRESS='demo@qq.com'
46 |
--------------------------------------------------------------------------------
/packages/funpi/plugins/swagger.js:
--------------------------------------------------------------------------------
1 | import fp from 'fastify-plugin';
2 | import fastifySwagger from '@fastify/swagger';
3 | import fastifySwaggerUi from '@fastify/swagger-ui';
4 |
5 | async function plugin(fastify) {
6 | await fastify.register(fastifySwagger, {
7 | mode: 'dynamic',
8 | swagger: {
9 | info: {
10 | title: `${process.env.APP_NAME}接口文档`,
11 | description: `${process.env.APP_NAME}接口文档`
12 | },
13 | host: '127.0.0.1',
14 | schemes: ['http'],
15 | consumes: ['application/json'],
16 | produces: ['application/json']
17 | }
18 | });
19 |
20 | await fastify.register(fastifySwaggerUi, {
21 | routePrefix: '/swagger',
22 | initOAuth: {},
23 | uiConfig: {
24 | docExpansion: 'none',
25 | deepLinking: false
26 | },
27 | staticCSP: true
28 | });
29 | }
30 | export default fp(plugin, {
31 | name: 'swagger'
32 | });
33 |
--------------------------------------------------------------------------------
/packages/api/apis/example/delete.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, httpConfig } from 'funpi';
2 |
3 | export default async (fastify) => {
4 | fnRoute(import.meta.url, fastify, {
5 | apiName: '删除案例',
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {
10 | id: fnSchema('id')
11 | },
12 | required: ['id']
13 | },
14 | // 执行函数
15 | apiHandler: async (req) => {
16 | try {
17 | const newsModel = fastify.mysql //
18 | .table('news');
19 |
20 | const result = await newsModel.clone().where('id', req.body.id).deleteData();
21 |
22 | return {
23 | ...httpConfig.INSERT_SUCCESS,
24 | data: result
25 | };
26 | } catch (err) {
27 | fastify.log.error(err);
28 | return httpConfig.SELECT_FAIL;
29 | }
30 | }
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/_meta.js:
--------------------------------------------------------------------------------
1 | export const metaConfig = {
2 | // 目录名称
3 | dirName: '管理员',
4 | // 接口名称
5 | apiNames: {
6 | getApis: '查询管理员接口权限',
7 | getMenus: '查询管理员菜单权限',
8 | adminLogin: '管理员登录',
9 | adminLoginLogSelectPage: '管理员登录日志-分页',
10 | adminActionLogSelectPage: '管理员操作日志-分页',
11 | adminInsert: '添加管理员',
12 | adminDelete: '删除管理员',
13 | adminSelectPage: '查询管理员-分页',
14 | adminUpdate: '更新管理员',
15 | apiSelectAll: '查询接口-全部',
16 | apiSelectPage: '查询接口-分页',
17 | menuDelete: '删除菜单',
18 | menuSelectAll: '查询菜单-全部',
19 | menuSelectPage: '查询菜单-分页',
20 | menuUpdate: '更新菜单',
21 | menuInsert: '添加菜单',
22 | roleDelete: '删除角色',
23 | roleInsert: '添加角色',
24 | roleSelectAll: '查询角色-全部',
25 | roleSelectPage: '查询角色-分页',
26 | roleUpdate: '更新角色',
27 | roleDetail: '角色详情',
28 | mailSelectPage: '查询邮件日志-分页',
29 | apiUpdateState: '更新接口状态'
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/packages/funpi/bootstrap/cors.js:
--------------------------------------------------------------------------------
1 | import fp from 'fastify-plugin';
2 | import fastifyCors from '@fastify/cors';
3 |
4 | async function plugin(fastify) {
5 | await fastify.register(fastifyCors, function () {
6 | return (req, callback) => {
7 | // 默认跨域,如果需要指定请求前缀,可以被传入的参数覆盖
8 | const newCorsConfig = {
9 | origin: req.headers.origin || req.headers.host || '*',
10 | methods: ['GET', 'OPTIONS', 'POST'],
11 | allowedHeaders: ['Content-Type', 'Authorization', 'authorization', 'token'],
12 | exposedHeaders: ['Content-Range', 'X-Content-Range', 'Authorization', 'authorization', 'token'],
13 | preflightContinue: false,
14 | strictPreflight: false,
15 | preflight: true,
16 | optionsSuccessStatus: 204,
17 | credentials: false
18 | };
19 |
20 | callback(null, newCorsConfig);
21 | };
22 | });
23 | }
24 | export default fp(plugin, { name: 'funpiCors' });
25 |
--------------------------------------------------------------------------------
/packages/funpi/tables/api.js:
--------------------------------------------------------------------------------
1 | export const tableName = '系统接口表';
2 | export const tableData = {
3 | pid: {
4 | name: '父级ID',
5 | type: 'bigInt',
6 | default: 0,
7 | isIndex: true,
8 | min: 1
9 | },
10 | pids: {
11 | name: '父级ID链',
12 | type: 'string',
13 | default: '0',
14 | max: 1000
15 | },
16 | name: {
17 | name: '名称',
18 | type: 'string',
19 | default: '',
20 | max: 100
21 | },
22 | value: {
23 | name: '值',
24 | type: 'string',
25 | default: '',
26 | max: 500
27 | },
28 | sort: {
29 | name: '字典排序',
30 | type: 'bigInt',
31 | default: 0,
32 | min: 0
33 | },
34 | describe: {
35 | name: '描述',
36 | type: 'string',
37 | default: '',
38 | max: 500
39 | },
40 | state: {
41 | name: '状态 0 正常 1 白名单 2 黑名单',
42 | type: 'int',
43 | default: 0,
44 | enum: [0, 1, 2]
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/packages/funpi/tables/role.js:
--------------------------------------------------------------------------------
1 | export const tableName = '系统角色表';
2 | export const tableData = {
3 | code: {
4 | name: '角色编码',
5 | type: 'string',
6 | default: '',
7 | max: 50,
8 | isIndex: true,
9 | pattern: '^[a-z][a-z0-9_]*$'
10 | },
11 | name: {
12 | name: '角色名称',
13 | type: 'string',
14 | default: '',
15 | max: 100
16 | },
17 | describe: {
18 | name: '角色描述',
19 | type: 'string',
20 | default: '',
21 | max: 500
22 | },
23 | menu_ids: {
24 | name: '角色菜单',
25 | type: 'mediumText',
26 | max: 50000
27 | },
28 | api_ids: {
29 | name: '角色接口',
30 | type: 'mediumText',
31 | max: 50000
32 | },
33 | sort: {
34 | name: '角色排序',
35 | type: 'bigInt',
36 | default: 1,
37 | min: 1
38 | },
39 | is_system: {
40 | name: '是否系统角色(不可删除)',
41 | type: 'tinyInt',
42 | default: 0,
43 | enum: [0, 1]
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/packages/funpi/apis/dict/categorySelectAll.js:
--------------------------------------------------------------------------------
1 | import { fnRoute } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {}
10 | },
11 | // 执行函数
12 | apiHandler: async (req) => {
13 | try {
14 | const dictCategoryModel = fastify.mysql //
15 | .table('sys_dict_category')
16 | .modify(function (db) {});
17 |
18 | const rows = await dictCategoryModel.clone().selectAll();
19 |
20 | return {
21 | ...httpConfig.SELECT_SUCCESS,
22 | data: {
23 | rows: rows
24 | }
25 | };
26 | } catch (err) {
27 | fastify.log.error(err);
28 | return httpConfig.SELECT_FAIL;
29 | }
30 | }
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/packages/funpi/tables/mailLog.js:
--------------------------------------------------------------------------------
1 | export const tableName = '邮件日志表';
2 | export const tableData = {
3 | login_email: {
4 | type: 'string',
5 | name: '登录邮箱',
6 | default: '',
7 | max: 100
8 | },
9 | from_name: {
10 | type: 'string',
11 | name: '发送者昵称',
12 | default: '',
13 | max: 100
14 | },
15 | from_email: {
16 | type: 'string',
17 | name: '发送者邮箱',
18 | default: '',
19 | max: 100
20 | },
21 | to_email: {
22 | name: '接收者邮箱',
23 | type: 'string',
24 | default: '',
25 | max: 5000
26 | },
27 | email_type: {
28 | name: '邮件类型',
29 | type: 'string',
30 | default: 'common',
31 | min: 1,
32 | max: 100
33 | },
34 | email_code: {
35 | name: '邮件识别码',
36 | type: 'string',
37 | min: 1,
38 | max: 100,
39 | default: ''
40 | },
41 | text_content: {
42 | name: '发送内容',
43 | type: 'string',
44 | default: '',
45 | max: 10000
46 | }
47 | };
48 |
--------------------------------------------------------------------------------
/packages/funpi/tables/adminActionLog.js:
--------------------------------------------------------------------------------
1 | export const tableName = '管理员操作日志表';
2 | export const tableData = {
3 | user_id: {
4 | name: '登录用户ID',
5 | type: 'bigInt',
6 | default: 0,
7 | isIndex: true,
8 | min: 0
9 | },
10 | username: {
11 | name: '用户名',
12 | type: 'string',
13 | default: '',
14 | max: 50
15 | },
16 | nickname: {
17 | name: '昵称',
18 | type: 'string',
19 | default: '',
20 | max: 50
21 | },
22 | role: {
23 | name: '角色',
24 | type: 'string',
25 | default: '',
26 | max: 50
27 | },
28 | ip: {
29 | name: 'ip',
30 | type: 'string',
31 | default: '',
32 | max: 50
33 | },
34 | ua: {
35 | name: 'ua',
36 | type: 'string',
37 | default: '',
38 | max: 500
39 | },
40 | api: {
41 | name: '访问接口',
42 | type: 'string',
43 | default: '',
44 | max: 200
45 | },
46 | params: {
47 | name: '请求参数',
48 | type: 'string',
49 | default: '',
50 | max: 5000
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/packages/api/apis/example/detail.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnField, httpConfig } from 'funpi';
2 | import { tableData } from '../../tables/example.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | apiName: '查询案例详情',
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | id: fnSchema('id')
12 | },
13 | required: ['id']
14 | },
15 |
16 | // 执行函数
17 | apiHandler: async (req) => {
18 | try {
19 | const newsModel = fastify.mysql.table('news');
20 |
21 | const result = await newsModel //
22 | .clone()
23 | .where({ id: req.body.id })
24 | .selectOne(fnField(tableData));
25 |
26 | return {
27 | ...httpConfig.SELECT_SUCCESS,
28 | data: result
29 | };
30 | } catch (err) {
31 | fastify.log.error(err);
32 | return httpConfig.SELECT_FAIL;
33 | }
34 | }
35 | });
36 | };
37 |
--------------------------------------------------------------------------------
/packages/funpi/tables/menu.js:
--------------------------------------------------------------------------------
1 | export const tableName = '系统菜单表';
2 | export const tableData = {
3 | pid: {
4 | name: '父级ID',
5 | type: 'bigInt',
6 | default: 0,
7 | isIndex: true,
8 | min: 0
9 | },
10 | image: {
11 | name: '菜单图标',
12 | type: 'string',
13 | default: '',
14 | max: 500
15 | },
16 | name: {
17 | name: '名称',
18 | type: 'string',
19 | default: '',
20 | max: 100
21 | },
22 | value: {
23 | name: '路由',
24 | type: 'string',
25 | default: '',
26 | isUnique: true,
27 | max: 500
28 | },
29 | sort: {
30 | name: '字典排序',
31 | type: 'bigInt',
32 | default: 100,
33 | min: 100
34 | },
35 | describe: {
36 | name: '描述',
37 | type: 'string',
38 | default: '',
39 | max: 500
40 | },
41 | is_open: {
42 | name: '是否公开',
43 | type: 'tinyInt',
44 | default: 0,
45 | enum: [0, 1]
46 | },
47 | is_system: {
48 | name: '是否系统账号(不可删除)',
49 | type: 'tinyInt',
50 | default: 0,
51 | enum: [0, 1]
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/packages/funpi/bootstrap/mail.js:
--------------------------------------------------------------------------------
1 | import fp from 'fastify-plugin';
2 | import nodemailer from 'nodemailer';
3 |
4 | async function plugin(fastify) {
5 | const mailTransport = await nodemailer.createTransport({
6 | host: process.env.MAIL_HOST,
7 | port: process.env.MAIL_PORT,
8 | pool: process.env.MAIL_POOL === '1' ? true : false,
9 | secure: process.env.MAIL_SECURE === '1' ? true : false,
10 | auth: {
11 | user: process.env.MAIL_USER,
12 | pass: process.env.MAIL_PASS
13 | }
14 | });
15 |
16 | // 发送邮件
17 | function sendMail(params) {
18 | return new Promise((resolve, reject) => {
19 | try {
20 | const result = mailTransport.sendMail({
21 | from: {
22 | name: process.env.MAIL_FROM_NAME,
23 | address: process.env.MAIL_FROM_EMAIL
24 | },
25 | ...params
26 | });
27 | resolve(result);
28 | } catch (err) {
29 | reject(err);
30 | }
31 | });
32 | }
33 |
34 | fastify.decorate('sendMail', sendMail);
35 | }
36 | export default fp(plugin, { name: 'funpiMail' });
37 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/roleDetail.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {
10 | code: fnSchema('code')
11 | },
12 | required: ['code']
13 | },
14 | // 执行函数
15 | apiHandler: async (req) => {
16 | try {
17 | const roleModel = fastify.mysql.table('sys_role').where('code', req.body.code);
18 |
19 | const result = await roleModel.clone().selectOne();
20 |
21 | if (!result?.id) {
22 | return {
23 | ...httpConfig.SELECT_FAIL,
24 | msg: '没有查到角色信息'
25 | };
26 | }
27 |
28 | return {
29 | ...httpConfig.SELECT_SUCCESS,
30 | data: result
31 | };
32 | } catch (err) {
33 | fastify.log.error(err);
34 | return httpConfig.SELECT_FAIL;
35 | }
36 | }
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/roleSelectAll.js:
--------------------------------------------------------------------------------
1 | import { fnRoute } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {}
10 | },
11 | // 执行函数
12 | apiHandler: async (req) => {
13 | try {
14 | const roleModel = fastify.mysql //
15 | .table('sys_role')
16 | .modify(function (db) {
17 | // 如果不是开发管理员查询,则排除掉开发角色
18 | if (req.session.role !== 'dev') {
19 | db.where('code', '<>', 'dev');
20 | }
21 | });
22 |
23 | const rows = await roleModel.clone().selectAll();
24 |
25 | return {
26 | ...httpConfig.SELECT_SUCCESS,
27 | data: {
28 | rows: rows
29 | }
30 | };
31 | } catch (err) {
32 | fastify.log.error(err);
33 | return httpConfig.SELECT_FAIL;
34 | }
35 | }
36 | });
37 | };
38 |
--------------------------------------------------------------------------------
/packages/admin/src/layouts/default/components/sideMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ menu.name }}
5 |
6 |
7 | {{ item.name }}
8 |
9 |
10 |
11 |
12 |
45 |
--------------------------------------------------------------------------------
/packages/funpi/apis/tool/tokenCheck.js:
--------------------------------------------------------------------------------
1 | import { fnRoute } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {},
10 | required: []
11 | },
12 | // 执行函数
13 | apiHandler: async (req) => {
14 | try {
15 | try {
16 | const jwtData = await req.jwtVerify();
17 | return {
18 | ...httpConfig.SUCCESS,
19 | data: {
20 | state: 'yes'
21 | },
22 | detail: jwtData
23 | };
24 | } catch (err) {
25 | fastify.log.error(err);
26 | return {
27 | ...httpConfig.SUCCESS,
28 | data: {
29 | state: 'no'
30 | }
31 | };
32 | }
33 | } catch (err) {
34 | fastify.log.error(err);
35 | return httpConfig.FAIL;
36 | }
37 | }
38 | });
39 | };
40 |
--------------------------------------------------------------------------------
/packages/funpi/schema/menu.js:
--------------------------------------------------------------------------------
1 | export const menuSchema = {
2 | title: '菜单字段',
3 | type: 'array',
4 | items: {
5 | title: '主菜单',
6 | type: 'object',
7 | properties: {
8 | path: {
9 | title: '菜单路径',
10 | type: 'string',
11 | pattern: '^\\/[a-z][a-z0-9\\/-]*$'
12 | },
13 | name: {
14 | title: '菜单名称',
15 | type: 'string'
16 | },
17 | children: {
18 | title: '子菜单',
19 | type: 'array',
20 | items: {
21 | type: 'object',
22 | properties: {
23 | path: {
24 | title: '子菜单路径',
25 | type: 'string',
26 | pattern: '^\\/[a-z][a-z0-9\\/-]*$'
27 | },
28 | name: {
29 | title: '菜单名称',
30 | type: 'string'
31 | }
32 | },
33 | additionalProperties: false,
34 | required: ['path', 'name']
35 | }
36 | }
37 | },
38 | additionalProperties: false,
39 | required: ['path', 'name', 'children']
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/packages/api/apis/example/update.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, httpConfig } from 'funpi';
2 | import { tableData } from '../../tables/example.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | apiName: '更新案例',
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | id: fnSchema('id'),
12 | title: fnSchema(tableData.title),
13 | content: fnSchema(tableData.content)
14 | },
15 | required: ['id']
16 | },
17 |
18 | // 执行函数
19 | apiHandler: async (req) => {
20 | try {
21 | const newsModel = fastify.mysql.table('news');
22 |
23 | const result = await newsModel //
24 | .clone()
25 | .where('id', req.body.id)
26 | .updateData({
27 | title: req.body.title,
28 | content: req.body.content
29 | });
30 |
31 | return {
32 | ...httpConfig.INSERT_SUCCESS,
33 | data: result
34 | };
35 | } catch (err) {
36 | fastify.log.error(err);
37 | return httpConfig.INSERT_FAIL;
38 | }
39 | }
40 | });
41 | };
42 |
--------------------------------------------------------------------------------
/packages/api/apis/example/insert.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, httpConfig } from 'funpi';
2 | import { tableData } from '../../tables/example.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | apiName: '添加案例',
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | title: fnSchema(tableData.title),
12 | content: fnSchema(tableData.content)
13 | },
14 | required: ['title', 'content']
15 | },
16 |
17 | // 执行函数
18 | apiHandler: async (req) => {
19 | const trx = await fastify.mysql.transaction();
20 | try {
21 | const newsModel = trx('example');
22 |
23 | const result = await newsModel //
24 | .clone()
25 | .insertData({
26 | title: req.body.title,
27 | content: req.body.content
28 | });
29 |
30 | await trx.commit();
31 | return {
32 | ...httpConfig.INSERT_SUCCESS,
33 | data: result
34 | };
35 | } catch (err) {
36 | await trx.rollback();
37 | fastify.log.error(err);
38 | return httpConfig.INSERT_FAIL;
39 | }
40 | }
41 | });
42 | };
43 |
--------------------------------------------------------------------------------
/packages/funpi/tables/dict.js:
--------------------------------------------------------------------------------
1 | export const tableName = '字典数据表';
2 | export const tableData = {
3 | category_id: {
4 | name: '分类ID',
5 | type: 'bigInt',
6 | default: 0,
7 | isIndex: true,
8 | min: 0
9 | },
10 | category_code: {
11 | name: '分类编码',
12 | type: 'string',
13 | default: '',
14 | isIndex: true,
15 | max: 50,
16 | pattern: '^[a-z][a-z0-9_-]*$'
17 | },
18 | code: {
19 | name: '字典编码',
20 | type: 'string',
21 | default: '',
22 | max: 50,
23 | pattern: '^[a-z][a-z0-9_-]*$'
24 | },
25 | name: {
26 | name: '字典名称',
27 | type: 'string',
28 | default: '',
29 | max: 100
30 | },
31 | value: {
32 | name: '字典值',
33 | type: 'string',
34 | default: '',
35 | max: 1000
36 | },
37 | symbol: {
38 | name: '数字类型或字符串类型',
39 | type: 'string',
40 | default: '',
41 | enum: ['string', 'number']
42 | },
43 | sort: {
44 | name: '字典排序',
45 | type: 'bigInt',
46 | default: 0,
47 | min: 0
48 | },
49 | describe: {
50 | name: '描述',
51 | type: 'string',
52 | default: '',
53 | max: 500
54 | },
55 | thumbnail: {
56 | name: '缩略图',
57 | type: 'string',
58 | default: '',
59 | max: 500
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/adminDelete.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {
10 | id: fnSchema('id')
11 | },
12 | required: ['id']
13 | },
14 | // 执行函数
15 | apiHandler: async (req) => {
16 | try {
17 | const adminModel = fastify.mysql.table('sys_admin').where({ id: req.body.id });
18 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
19 |
20 | const adminData = await adminModel.clone().selectOne(['id']);
21 | if (!adminData?.id) {
22 | return httpConfig.NO_DATA;
23 | }
24 |
25 | const result = await adminModel.deleteData();
26 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req)));
27 |
28 | return {
29 | ...httpConfig.DELETE_SUCCESS,
30 | data: result
31 | };
32 | } catch (err) {
33 | fastify.log.error(err);
34 | return httpConfig.DELETE_FAIL;
35 | }
36 | }
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/packages/funpi/apis/dict/dictDelete.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {
10 | id: fnSchema('id')
11 | },
12 | required: ['id']
13 | },
14 | // 执行函数
15 | apiHandler: async (req) => {
16 | try {
17 | const dictModel = fastify.mysql.table('sys_dict').where({ id: req.body.id });
18 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
19 |
20 | const dictData = await dictModel.clone().selectOne(['id']);
21 | if (!dictData?.id) {
22 | return httpConfig.NO_DATA;
23 | }
24 |
25 | const result = await dictModel.clone().deleteData();
26 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req)));
27 |
28 | return {
29 | ...httpConfig.DELETE_SUCCESS,
30 | data: result
31 | };
32 | } catch (err) {
33 | fastify.log.error(err);
34 | return httpConfig.DELETE_FAIL;
35 | }
36 | }
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/packages/funpi/config/http.js:
--------------------------------------------------------------------------------
1 | export const httpConfig = {
2 | SUCCESS: { symbol: 'SUCCESS', code: 0, msg: '操作成功' },
3 | INSERT_SUCCESS: { symbol: 'INSERT_SUCCESS', code: 0, msg: '添加成功' },
4 | SELECT_SUCCESS: { symbol: 'SELECT_SUCCESS', code: 0, msg: '查询成功' },
5 | UPDATE_SUCCESS: { symbol: 'UPDATE_SUCCESS', code: 0, msg: '更新成功' },
6 | DELETE_SUCCESS: { symbol: 'DELETE_SUCCESS', code: 0, msg: '删除成功' },
7 | FAIL: { symbol: 'FAIL', code: 1, msg: '操作失败' },
8 | INSERT_FAIL: { symbol: 'INSERT_FAIL', code: 1, msg: '添加失败' },
9 | SELECT_FAIL: { symbol: 'SELECT_FAIL', code: 1, msg: '查询失败' },
10 | UPDATE_FAIL: { symbol: 'UPDATE_FAIL', code: 1, msg: '更新失败' },
11 | DELETE_FAIL: { symbol: 'DELETE_FAIL', code: 1, msg: '删除失败' },
12 | INFO: { symbol: 'INFO', code: 11, msg: '提示' },
13 | WARN: { symbol: 'WARN', code: 12, msg: '警告' },
14 | ERROR: { symbol: 'ERROR', code: 13, msg: '错误' },
15 | NOT_LOGIN: { symbol: 'NOT_LOGIN', code: 14, msg: '未登录' },
16 | API_DISABLED: { symbol: 'API_DISABLED', code: 15, msg: '接口已禁用' },
17 | NO_FILE: { symbol: 'NO_FILE', code: 17, msg: '文件不存在' },
18 | NO_API: { symbol: 'NO_API', code: 18, msg: '接口不存在' },
19 | NO_USER: { symbol: 'NO_USER', code: 19, msg: '用户不存在' },
20 | NO_DATA: { symbol: 'NO_DATA', code: 20, msg: '数据不存在' },
21 | NO_PERMISSION: { symbol: 'NO_PERMISSION', code: 21, msg: '无操作权限' },
22 | PARAMS_SIGN_FAIL: { symbol: 'PARAMS_SIGN_FAIL', code: 22, msg: '参数签名错误' }
23 | };
24 |
--------------------------------------------------------------------------------
/packages/api/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | esdata
6 | data
7 | .cache
8 | lab
9 | labs
10 | cache
11 | .VSCodeCounter
12 | oclif.manifest.json
13 | dist
14 | report.html
15 | dist-ssr
16 | *.local
17 | .vscode-test/
18 | *.vsix
19 | out
20 | CHANGELOG.md
21 | note.md
22 | .changeset
23 | addons2
24 |
25 | # Editor directories and files
26 | .vscode/*
27 | .cache
28 | .yicode/auto-imports.d.ts
29 | !.vscode/extensions.json
30 | .idea
31 | .DS_Store
32 | *.suo
33 | *.ntvs*
34 | *.njsproj
35 | *.sln
36 | *.sw?
37 |
38 | # Runtime data
39 | pids
40 | *.pid
41 | *.seed
42 |
43 | # Directory for instrumented libs generated by jscoverage/JSCover
44 | lib-cov
45 |
46 | # Coverage directory used by tools like istanbul
47 | coverage
48 |
49 | # nyc test coverage
50 | .nyc_output
51 |
52 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
53 | .grunt
54 |
55 | # node-waf configuration
56 | .lock-wscript
57 |
58 | # Compiled binary addons (http://nodejs.org/api/addons.html)
59 | build/Release
60 |
61 | # Dependency directories
62 | node_modules
63 | jspm_packages
64 |
65 | # Optional npm cache directory
66 | .npm
67 |
68 | # Optional REPL history
69 | .node_repl_history
70 |
71 | # 0x
72 | profile-*
73 |
74 | # mac files
75 | .DS_Store
76 |
77 | # vim swap files
78 | *.swp
79 |
80 | # webstorm
81 | .idea
82 |
83 | # vscode
84 | .vscode
85 | *code-workspace
86 |
87 | # clinic
88 | profile*
89 | *clinic*
90 | *flamegraph*
91 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/apiSelectPage.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {
10 | page: fnSchema('page'),
11 | limit: fnSchema('limit')
12 | }
13 | },
14 | // 执行函数
15 | apiHandler: async (req) => {
16 | try {
17 | const apiModel = fastify.mysql.table('sys_api');
18 |
19 | const { totalCount } = await apiModel.clone().selectCount();
20 |
21 | const rows = await apiModel
22 | //
23 | .clone()
24 | .orderBy('created_at', 'desc')
25 | .selectData(req.body.page, req.body.limit);
26 |
27 | return {
28 | ...httpConfig.SELECT_SUCCESS,
29 | data: {
30 | total: totalCount,
31 | rows: rows,
32 | page: req.body.page,
33 | limit: req.body.limit
34 | }
35 | };
36 | } catch (err) {
37 | fastify.log.error(err);
38 | return httpConfig.SELECT_FAIL;
39 | }
40 | }
41 | });
42 | };
43 |
--------------------------------------------------------------------------------
/packages/admin/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | esdata
6 | data
7 | .cache
8 | lab
9 | labs
10 | cache
11 | .VSCodeCounter
12 | oclif.manifest.json
13 | dist
14 | report.html
15 | dist-ssr
16 | *.local
17 | .vscode-test/
18 | *.vsix
19 | out
20 | CHANGELOG.md
21 | note.md
22 | .changeset
23 |
24 | # Editor directories and files
25 | .vscode/*
26 | .cache
27 | .yicode/auto-imports.d.ts
28 | !.vscode/extensions.json
29 | .idea
30 | .DS_Store
31 | *.suo
32 | *.ntvs*
33 | *.njsproj
34 | *.sln
35 | *.sw?
36 | *.lock
37 | *lock.*
38 |
39 | # Runtime data
40 | pids
41 | *.pid
42 | *.seed
43 |
44 | # Directory for instrumented libs generated by jscoverage/JSCover
45 | lib-cov
46 |
47 | # Coverage directory used by tools like istanbul
48 | coverage
49 |
50 | # nyc test coverage
51 | .nyc_output
52 |
53 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
54 | .grunt
55 |
56 | # node-waf configuration
57 | .lock-wscript
58 |
59 | # Compiled binary addons (http://nodejs.org/api/addons.html)
60 | build/Release
61 |
62 | # Dependency directories
63 | node_modules
64 | jspm_packages
65 |
66 | # Optional npm cache directory
67 | .npm
68 |
69 | # Optional REPL history
70 | .node_repl_history
71 |
72 | # 0x
73 | profile-*
74 |
75 | # mac files
76 | .DS_Store
77 |
78 | # vim swap files
79 | *.swp
80 |
81 | # webstorm
82 | .idea
83 |
84 | # vscode
85 | .vscode
86 | *code-workspace
87 |
88 | # clinic
89 | profile*
90 | *clinic*
91 | *flamegraph*
92 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | esdata
6 | data
7 | .cache
8 | lab
9 | labs
10 | cache
11 | .VSCodeCounter
12 | oclif.manifest.json
13 | dist
14 | report.html
15 | dist-ssr
16 | *.local
17 | .vscode-test/
18 | *.vsix
19 | out
20 | CHANGELOG.md
21 | note.md
22 | .changeset
23 | addons2
24 | pnpm-lock.yaml
25 | bun.lock
26 |
27 | # Editor directories and files
28 | .vscode/*
29 | .cache
30 | .yicode/auto-imports.d.ts
31 | !.vscode/extensions.json
32 | .idea
33 | .DS_Store
34 | *.suo
35 | *.ntvs*
36 | *.njsproj
37 | *.sln
38 | *.sw?
39 |
40 | # Runtime data
41 | pids
42 | *.pid
43 | *.seed
44 |
45 | # Directory for instrumented libs generated by jscoverage/JSCover
46 | lib-cov
47 |
48 | # Coverage directory used by tools like istanbul
49 | coverage
50 |
51 | # nyc test coverage
52 | .nyc_output
53 |
54 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
55 | .grunt
56 |
57 | # node-waf configuration
58 | .lock-wscript
59 |
60 | # Compiled binary addons (http://nodejs.org/api/addons.html)
61 | build/Release
62 |
63 | # Dependency directories
64 | node_modules
65 | jspm_packages
66 |
67 | # Optional npm cache directory
68 | .npm
69 |
70 | # Optional REPL history
71 | .node_repl_history
72 |
73 | # 0x
74 | profile-*
75 |
76 | # mac files
77 | .DS_Store
78 |
79 | # vim swap files
80 | *.swp
81 |
82 | # webstorm
83 | .idea
84 |
85 | # vscode
86 | .vscode
87 | *code-workspace
88 |
89 | # clinic
90 | profile*
91 | *clinic*
92 | *flamegraph*
93 |
--------------------------------------------------------------------------------
/packages/funpi/apis/dict/dictSelectAll.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 | import { tableData } from '../../tables/dict.js';
4 |
5 | export default async (fastify) => {
6 | fnRoute(import.meta.url, fastify, {
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | category_code: fnSchema(tableData.category_code)
12 | }
13 | },
14 | // 执行函数
15 | apiHandler: async (req) => {
16 | try {
17 | const dictModel = fastify.mysql
18 | .table('sys_dict')
19 | .where('category_code', req.body.category_code)
20 | .modify(function (db) {});
21 |
22 | const rowsTemp = await dictModel.clone().selectAll();
23 |
24 | const rows = rowsTemp?.map((item) => {
25 | if (item.symbol === 'number') {
26 | item.value = Number(item.value);
27 | }
28 | return item;
29 | });
30 | return {
31 | ...httpConfig.SELECT_SUCCESS,
32 | data: {
33 | rows: rows
34 | }
35 | };
36 | } catch (err) {
37 | fastify.log.error(err);
38 | return httpConfig.SELECT_FAIL;
39 | }
40 | }
41 | });
42 | };
43 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/menuSelectPage.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {
10 | page: fnSchema('page'),
11 | limit: fnSchema('limit')
12 | },
13 | required: []
14 | },
15 | // 执行函数
16 | apiHandler: async (req) => {
17 | try {
18 | const menuModel = fastify.mysql //
19 | .table('sys_menu');
20 |
21 | const { totalCount } = await menuModel.clone().selectCount();
22 |
23 | const rows = await menuModel
24 | //
25 | .clone()
26 | .orderBy('created_at', 'desc')
27 | .selectData(req.body.page, req.body.limit);
28 |
29 | return {
30 | ...httpConfig.SELECT_SUCCESS,
31 | data: {
32 | total: totalCount,
33 | rows: rows,
34 | page: req.body.page,
35 | limit: req.body.limit
36 | }
37 | };
38 | } catch (err) {
39 | fastify.log.error(err);
40 | return httpConfig.SELECT_FAIL;
41 | }
42 | }
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/packages/admin/yite.config.js:
--------------------------------------------------------------------------------
1 | import { vitePluginForArco } from '@arco-plugins/vite-vue';
2 | export const yiteConfig = {
3 | devtool: false,
4 | // 自动导入解析
5 | autoImport: {
6 | resolvers: [
7 | {
8 | name: 'ArcoResolver',
9 | options: {}
10 | }
11 | ],
12 | imports: [
13 | {
14 | '@arco-design/web-vue': [
15 | //
16 | 'Message',
17 | 'Modal',
18 | 'Notification',
19 | 'Drawer'
20 | ]
21 | }
22 | ]
23 | },
24 | // 自动组件解析
25 | autoComponent: {
26 | resolvers: [
27 | {
28 | name: 'ArcoResolver',
29 | options: {
30 | sideEffect: true
31 | }
32 | }
33 | ]
34 | },
35 | // webpack 配置
36 | viteConfig: {
37 | plugins: [
38 | vitePluginForArco({
39 | style: 'css'
40 | })
41 | ],
42 | optimizeDeps: {
43 | include: [
44 | //
45 | 'es-toolkit/compat',
46 | 'es-toolkit',
47 | 'vue-i18n',
48 | 'js-md5',
49 | 'axios',
50 | 'date-fns',
51 | 'date-fns/locale',
52 | '@arco-design/web-vue/es/icon',
53 | 'store2',
54 | '@arco-design/web-vue'
55 | ]
56 | }
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/packages/funpi/config/menu.js:
--------------------------------------------------------------------------------
1 | export const menuConfig = [
2 | {
3 | path: '/admin',
4 | name: '管理数据',
5 | children: [
6 | {
7 | path: '/internal/admin',
8 | name: '管理员'
9 | }
10 | ]
11 | },
12 | {
13 | path: '/dict',
14 | name: '字典管理',
15 | children: [
16 | {
17 | path: '/internal/dict-category',
18 | name: '字典分类'
19 | },
20 | {
21 | path: '/internal/dict',
22 | name: '字典列表'
23 | }
24 | ]
25 | },
26 | {
27 | path: '/log',
28 | name: '日志数据',
29 | children: [
30 | {
31 | path: '/internal/admin-login-log',
32 | name: '登录日志'
33 | },
34 | {
35 | path: '/internal/admin-action-log',
36 | name: '操作日志'
37 | },
38 | {
39 | path: '/internal/mail-log',
40 | name: '邮件日志'
41 | }
42 | ]
43 | },
44 | {
45 | path: '/permission',
46 | name: '权限数据',
47 | children: [
48 | {
49 | path: '/internal/menu',
50 | name: '菜单列表'
51 | },
52 | {
53 | path: '/internal/api',
54 | name: '接口列表'
55 | },
56 | {
57 | path: '/internal/role',
58 | name: '角色管理'
59 | }
60 | ]
61 | }
62 | ];
63 |
--------------------------------------------------------------------------------
/packages/api/addons/weixin/apis/pay/select.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnField, httpConfig } from 'funpi';
2 |
3 | export default async (fastify) => {
4 | fnRoute(import.meta.url, fastify, {
5 | apiName: '查询案例',
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {
10 | page: fnSchema('page'),
11 | limit: fnSchema('limit')
12 | },
13 | required: []
14 | },
15 |
16 | // 执行函数
17 | apiHandler: async (req) => {
18 | try {
19 | const newsModel = fastify.mysql //
20 | .table('news');
21 |
22 | // 记录总数
23 | const { totalCount } = await newsModel
24 | .clone() //
25 | .selectCount();
26 |
27 | // 记录列表
28 | const rows = await newsModel
29 | .clone() //
30 | .orderBy('created_at', 'desc')
31 | .selectData(req.body.page, req.body.limit);
32 |
33 | return {
34 | ...httpConfig.SELECT_SUCCESS,
35 | data: {
36 | total: totalCount,
37 | rows: rows,
38 | page: req.body.page,
39 | limit: req.body.limit
40 | }
41 | };
42 | } catch (err) {
43 | fastify.log.error(err);
44 | return httpConfig.SELECT_FAIL;
45 | }
46 | }
47 | });
48 | };
49 |
--------------------------------------------------------------------------------
/packages/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@funpi/api",
3 | "version": "7.20.27",
4 | "description": "FunPi(放屁) - 接口端",
5 | "main": "funpi.js",
6 | "type": "module",
7 | "private": false,
8 | "publishConfig": {
9 | "access": "public",
10 | "registry": "https://registry.npmjs.org"
11 | },
12 | "scripts": {
13 | "dev": "bun --watch --env-file=./.env.development funpi.js",
14 | "server": "pm2 start pm2.config.cjs",
15 | "ct": "bun ./scripts/checkTable.js",
16 | "syncDb:dev": "bun --env-file=./.env.development ./scripts/syncMysql.js",
17 | "syncDb:prod": "bun --env-file=./.env.production ./scripts/syncMysql.js"
18 | },
19 | "keywords": [
20 | "api",
21 | "nodejs",
22 | "fastify"
23 | ],
24 | "files": [
25 | "apis/",
26 | "config/",
27 | "plugins/",
28 | "public/",
29 | "scripts/",
30 | "tables/",
31 | ".env.development",
32 | ".env.production",
33 | ".gitignore",
34 | ".npmrc",
35 | ".prettier",
36 | "funpi.js",
37 | "LICENSE",
38 | "package.json",
39 | "pm2.config.cjs",
40 | "README.md"
41 | ],
42 | "author": "chensuiyi ",
43 | "homepage": "https://chensuiyi.me",
44 | "repository": {
45 | "type": "git",
46 | "url": "https://github.com/chenbimo/yicode.git"
47 | },
48 | "dependencies": {
49 | "dotenv": "^16.5.0",
50 | "funpi": "workspace:^"
51 | },
52 | "gitHead": "1c28c0de7c0af8aa4582c45ab2d98e66c597c7a1"
53 | }
54 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/adminLoginLogSelectPage.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnField } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 | import { tableData } from '../../tables/adminLoginLog.js';
4 |
5 | export default async (fastify) => {
6 | fnRoute(import.meta.url, fastify, {
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | page: fnSchema('page'),
12 | limit: fnSchema('limit')
13 | }
14 | },
15 | // 执行函数
16 | apiHandler: async (req) => {
17 | try {
18 | const adminLoginLogModel = fastify.mysql //
19 | .table('sys_admin_login_log');
20 |
21 | const { totalCount } = await adminLoginLogModel.clone().selectCount();
22 | const rows = await adminLoginLogModel
23 | //
24 | .clone()
25 | .orderBy('created_at', 'desc')
26 | .selectData(req.body.page, req.body.limit, fnField(tableData));
27 |
28 | return {
29 | ...httpConfig.SELECT_SUCCESS,
30 | data: {
31 | total: totalCount,
32 | rows: rows,
33 | page: req.body.page,
34 | limit: req.body.limit
35 | }
36 | };
37 | } catch (err) {
38 | fastify.log.error(err);
39 | return httpConfig.SELECT_FAIL;
40 | }
41 | }
42 | });
43 | };
44 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/adminActionLogSelectPage.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnField } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 | import { tableData } from '../../tables/adminActionLog.js';
4 |
5 | export default async (fastify) => {
6 | fnRoute(import.meta.url, fastify, {
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | page: fnSchema('page'),
12 | limit: fnSchema('limit')
13 | }
14 | },
15 | // 执行函数
16 | apiHandler: async (req) => {
17 | try {
18 | const adminActionLogModel = fastify.mysql //
19 | .table('sys_admin_action_log');
20 |
21 | const { totalCount } = await adminActionLogModel.clone().selectCount();
22 | const rows = await adminActionLogModel
23 | //
24 | .clone()
25 | .orderBy('created_at', 'desc')
26 | .selectData(req.body.page, req.body.limit, fnField(tableData));
27 |
28 | return {
29 | ...httpConfig.SELECT_SUCCESS,
30 | data: {
31 | total: totalCount,
32 | rows: rows,
33 | page: req.body.page,
34 | limit: req.body.limit
35 | }
36 | };
37 | } catch (err) {
38 | fastify.log.error(err);
39 | return httpConfig.SELECT_FAIL;
40 | }
41 | }
42 | });
43 | };
44 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/adminSelectPage.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnField } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 | import { tableData } from '../../tables/admin.js';
4 |
5 | export default async (fastify) => {
6 | fnRoute(import.meta.url, fastify, {
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | page: fnSchema('page'),
12 | limit: fnSchema('limit')
13 | }
14 | },
15 | // 执行函数
16 | apiHandler: async (req) => {
17 | try {
18 | const adminModel = fastify.mysql //
19 | .table('sys_admin')
20 | .where('username', '<>', 'dev');
21 |
22 | const { totalCount } = await adminModel.clone().selectCount();
23 | const rows = await adminModel
24 | //
25 | .clone()
26 | .orderBy('created_at', 'desc')
27 | .selectData(req.body.page, req.body.limit, fnField(tableData, ['password']));
28 |
29 | return {
30 | ...httpConfig.SELECT_SUCCESS,
31 | data: {
32 | total: totalCount,
33 | rows: rows,
34 | page: req.body.page,
35 | limit: req.body.limit
36 | }
37 | };
38 | } catch (err) {
39 | fastify.log.error(err);
40 | return httpConfig.SELECT_FAIL;
41 | }
42 | }
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/packages/api/apis/example/select.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnField, httpConfig } from 'funpi';
2 | import { tableData } from '../../tables/example.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | apiName: '查询案例',
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | page: fnSchema('page'),
12 | limit: fnSchema('limit')
13 | },
14 | required: []
15 | },
16 |
17 | // 执行函数
18 | apiHandler: async (req) => {
19 | try {
20 | const newsModel = fastify.mysql //
21 | .table('news');
22 |
23 | // 记录总数
24 | const { totalCount } = await newsModel
25 | .clone() //
26 | .selectCount();
27 |
28 | // 记录列表
29 | const rows = await newsModel
30 | .clone() //
31 | .orderBy('created_at', 'desc')
32 | .selectData(req.body.page, req.body.limit, fnField(tableData));
33 |
34 | return {
35 | ...httpConfig.SELECT_SUCCESS,
36 | data: {
37 | total: totalCount,
38 | rows: rows,
39 | page: req.body.page,
40 | limit: req.body.limit
41 | }
42 | };
43 | } catch (err) {
44 | fastify.log.error(err);
45 | return httpConfig.SELECT_FAIL;
46 | }
47 | }
48 | });
49 | };
50 |
--------------------------------------------------------------------------------
/packages/api/apis/example/_ffffff.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnField, httpConfig } from 'funpi';
2 | import { tableData } from '../../tables/example.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | apiName: '查询案例',
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | page: fnSchema('page'),
12 | limit: fnSchema('limit')
13 | },
14 | required: []
15 | },
16 |
17 | // 执行函数
18 | apiHandler: async (req) => {
19 | try {
20 | const newsModel = fastify.mysql //
21 | .table('news');
22 |
23 | // 记录总数
24 | const { totalCount } = await newsModel
25 | .clone() //
26 | .selectCount();
27 |
28 | // 记录列表
29 | const rows = await newsModel
30 | .clone() //
31 | .orderBy('created_at', 'desc')
32 | .selectData(req.body.page, req.body.limit, fnField(tableData));
33 |
34 | return {
35 | ...httpConfig.SELECT_SUCCESS,
36 | data: {
37 | total: totalCount,
38 | rows: rows,
39 | page: req.body.page,
40 | limit: req.body.limit
41 | }
42 | };
43 | } catch (err) {
44 | fastify.log.error(err);
45 | return httpConfig.SELECT_FAIL;
46 | }
47 | }
48 | });
49 | };
50 |
--------------------------------------------------------------------------------
/packages/admin/src/plugins/http.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { Message } from '@arco-design/web-vue';
3 |
4 | const $Http = axios.create({
5 | method: 'POST',
6 | baseURL: import.meta.env.VITE_HOST,
7 | timeout: 1000 * 60,
8 | withCredentials: false,
9 | responseType: 'json',
10 | responseEncoding: 'utf8',
11 | headers: {
12 | 'Content-Type': 'application/json; charset=utf-8'
13 | },
14 | transformRequest: [
15 | (data, headers) => {
16 | const data2 = {};
17 | for (let key in data) {
18 | if (Object.prototype.hasOwnProperty.call(data, key) && [null, undefined].includes(data[key]) === false) {
19 | data2[key] = data[key];
20 | }
21 | }
22 | return JSON.stringify(data2);
23 | }
24 | ]
25 | });
26 | // 添加请求拦截器
27 | $Http.interceptors.request.use(
28 | function (config) {
29 | const token = $Storage.local.get('token');
30 | if (token) {
31 | config.headers.authorization = 'Bearer ' + token;
32 | }
33 | return config;
34 | },
35 | function (err) {
36 | return Promise.reject(err);
37 | }
38 | );
39 |
40 | // 添加响应拦截器
41 | $Http.interceptors.response.use(
42 | function (res) {
43 | if (res.data.code === 0) {
44 | return Promise.resolve(res.data);
45 | }
46 | if (res.data.symbol === 'NOT_LOGIN') {
47 | location.href = location.origin + '/#/internal/login';
48 | }
49 | return Promise.reject(res.data);
50 | },
51 | function (err) {
52 | Message.error(err.message);
53 | return Promise.reject(err);
54 | }
55 | );
56 | export { $Http };
57 |
--------------------------------------------------------------------------------
/packages/funpi/plugins/logger.js:
--------------------------------------------------------------------------------
1 | // 核心模块
2 | import { resolve } from 'pathe';
3 | // 外部模块
4 | import winston from 'winston';
5 | import 'winston-daily-rotate-file';
6 |
7 | import { appDir } from '../config/path.js';
8 |
9 | const fileConfig = {
10 | dirname: resolve(appDir, 'logs'),
11 | filename: '%DATE%.log',
12 | datePattern: 'YYYY-MM-DD',
13 | zippedArchive: false,
14 | maxSize: '50m'
15 | };
16 |
17 | const fileTransport = new winston.transports.DailyRotateFile(fileConfig);
18 |
19 | const configParams = {
20 | levels: {
21 | fatal: 0,
22 | error: 1,
23 | warn: 2,
24 | info: 3,
25 | trace: 4,
26 | debug: 5
27 | },
28 | level: process.env.LOG_LEVEL || 'warn',
29 | format: winston.format.combine(
30 | winston.format.timestamp({
31 | format: () => {
32 | return new Date().toLocaleString('zh-CN', {
33 | timeZone: process.env.TIMEZONE,
34 | hour12: false,
35 | year: 'numeric',
36 | month: '2-digit',
37 | day: '2-digit',
38 | hour: '2-digit',
39 | minute: '2-digit',
40 | second: '2-digit'
41 | });
42 | }
43 | }),
44 | winston.format.json()
45 | ),
46 | transports: [],
47 | exitOnError: false
48 | };
49 |
50 | // 如果是产品环境,则将日志写到文件中
51 | // 如果是开发环境,则直接打印日志
52 | if (process.env.NODE_ENV === 'production') {
53 | configParams.transports = [fileTransport];
54 | } else {
55 | configParams.transports = [new winston.transports.Console(), fileTransport];
56 | }
57 |
58 | const logger = winston.createLogger(configParams);
59 |
60 | export default logger;
61 |
--------------------------------------------------------------------------------
/packages/admin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@funpi/admin",
3 | "version": "7.20.27",
4 | "description": "FunPi(放屁) - 后台管理",
5 | "sideEffects": true,
6 | "type": "module",
7 | "private": false,
8 | "license": "Apache-2.0",
9 | "publishConfig": {
10 | "access": "public",
11 | "registry": "https://registry.npmjs.org"
12 | },
13 | "author": "chensuiyi ",
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/chenbimo/yicode.git"
17 | },
18 | "homepage": "https://chensuiyi.me",
19 | "scripts": {
20 | "dev": "yite --command=dev --envfile=development --workdir=./",
21 | "build": "yite --command=build --envfile=production --workdir=./",
22 | "update:template": "yite --command=update --project-type=yiadmin"
23 | },
24 | "keywords": [
25 | "lodash",
26 | "utils",
27 | "helper",
28 | "help"
29 | ],
30 | "files": [
31 | "public/",
32 | "src/",
33 | ".env.development",
34 | ".env.production",
35 | ".gitignore",
36 | ".prettier",
37 | ".npmrc",
38 | "index.html",
39 | "jsconfig.json",
40 | "LICENSE",
41 | "package.json",
42 | "README.md",
43 | "yite.config.js"
44 | ],
45 | "dependencies": {
46 | "@arco-design/web-vue": "^2.57.0",
47 | "@arco-plugins/vite-vue": "^1.4.5",
48 | "@iconify/json": "^2.2.343",
49 | "axios": "^1.9.0",
50 | "date-fns": "^4.1.0",
51 | "es-toolkit": "^1.38.0",
52 | "js-md5": "^0.8.3",
53 | "store2": "^2.14.4",
54 | "yite-cli": "^4.7.0"
55 | },
56 | "gitHead": "1c28c0de7c0af8aa4582c45ab2d98e66c597c7a1"
57 | }
58 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/roleDelete.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {
10 | id: fnSchema('id')
11 | },
12 | required: ['id']
13 | },
14 | // 执行函数
15 | apiHandler: async (req) => {
16 | try {
17 | const roleModel = fastify.mysql.table('sys_role').where('id', req.body.id);
18 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
19 |
20 | const roleData = await roleModel.clone().selectOne(['id', 'is_system']);
21 | if (!roleData?.id) {
22 | return httpConfig.NO_DATA;
23 | }
24 |
25 | if (roleData.is_system === 1) {
26 | return {
27 | ...httpConfig.DELETE_FAIL,
28 | msg: '系统角色,无法删除'
29 | };
30 | }
31 |
32 | const result = await roleModel.clone().deleteData();
33 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req)));
34 |
35 | // 生成新的权限
36 | await fastify.cacheRoleData();
37 |
38 | return {
39 | ...httpConfig.DELETE_SUCCESS,
40 | data: result
41 | };
42 | } catch (err) {
43 | fastify.log.error(err);
44 | return httpConfig.DELETE_FAIL;
45 | }
46 | }
47 | });
48 | };
49 |
--------------------------------------------------------------------------------
/packages/funpi/tables/admin.js:
--------------------------------------------------------------------------------
1 | export const tableName = '系统管理员表';
2 | export const tableData = {
3 | role: {
4 | name: '角色代号',
5 | type: 'string',
6 | default: '',
7 | min: 1,
8 | max: 50,
9 | pattern: '^[a-z][a-z0-9_-]*$'
10 | },
11 | username: {
12 | name: '用户名',
13 | type: 'string',
14 | default: '',
15 | min: 1,
16 | max: 20,
17 | pattern: '^[a-z][a-zA-Z0-9_-]*$'
18 | },
19 | password: {
20 | name: '密码',
21 | type: 'string',
22 | default: '',
23 | min: 6,
24 | max: 300
25 | },
26 | nickname: {
27 | name: '昵称',
28 | type: 'string',
29 | default: '',
30 | min: 1,
31 | max: 30
32 | },
33 | phone: {
34 | name: '手机号',
35 | type: 'string',
36 | default: '',
37 | min: 1,
38 | max: 30
39 | },
40 | weixin: {
41 | name: '微信号',
42 | type: 'string',
43 | default: '',
44 | min: 6,
45 | max: 30,
46 | pattern: '^[a-zA-Z][-_a-zA-Z0-9]{5,30}$'
47 | },
48 | qq: {
49 | name: 'QQ号',
50 | type: 'string',
51 | default: '',
52 | min: 5,
53 | max: 20,
54 | pattern: '^\\d{5,}$'
55 | },
56 | email: {
57 | name: '邮箱',
58 | type: 'string',
59 | default: '',
60 | min: 5,
61 | max: 50,
62 | pattern: '^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$'
63 | },
64 | avatar: {
65 | name: '头像',
66 | type: 'string',
67 | default: '',
68 | min: 0,
69 | max: 300
70 | },
71 | is_system: {
72 | name: '是否系统数据(不可删除)',
73 | type: 'tinyInt',
74 | default: 0,
75 | enum: [0, 1]
76 | }
77 | };
78 |
--------------------------------------------------------------------------------
/packages/funpi/config/env.js:
--------------------------------------------------------------------------------
1 | export const envConfig = {
2 | // 项目模式
3 | NODE_ENV: process.env.NODE_ENV,
4 | // 应用名称
5 | APP_NAME: process.env.APP_NAME,
6 | // 加密盐
7 | MD5_SALT: process.env.MD5_SALT,
8 | // 监听端口
9 | APP_PORT: Number(process.env.APP_PORT),
10 | // 监听主机
11 | LISTEN_HOST: process.env.LISTEN_HOST,
12 | // 超级管理员密码
13 | DEV_PASSWORD: process.env.DEV_PASSWORD,
14 | // 请求体大小 10M
15 | BODY_LIMIT: Number(process.env.BODY_LIMIT),
16 | // 是否进行参数验证
17 | PARAMS_CHECK: process.env.PARAMS_CHECK,
18 | // 日志等级
19 | LOG_LEVEL: process.env.LOG_LEVEL,
20 | // 数据库表主键方案
21 | TABLE_PRIMARY_KEY: process.env.TABLE_PRIMARY_KEY,
22 | // 时区
23 | TIMEZONE: process.env.TIMEZONE,
24 | // 数据库配置
25 | MYSQL_HOST: process.env.MYSQL_HOST,
26 | MYSQL_PORT: Number(process.env.MYSQL_PORT),
27 | MYSQL_DB: process.env.MYSQL_DB,
28 | MYSQL_USERNAME: process.env.MYSQL_USERNAME,
29 | MYSQL_PASSWORD: process.env.MYSQL_PASSWORD,
30 | // Redis配置
31 | REDIS_HOST: process.env.REDIS_HOST,
32 | REDIS_PORT: Number(process.env.REDIS_PORT),
33 | REDIS_USERNAME: process.env.REDIS_USERNAME,
34 | REDIS_PASSWORD: process.env.REDIS_PASSWORD,
35 | REDIS_DB: Number(process.env.REDIS_DB),
36 | REDIS_KEY_PREFIX: process.env.REDIS_KEY_PREFIX,
37 | // JWT配置
38 | JWT_SECRET: process.env.JWT_SECRET,
39 | JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN,
40 | JWT_ALGORITHM: process.env.JWT_ALGORITHM,
41 | // 邮件配置
42 | MAIL_HOST: process.env.MAIL_HOST,
43 | MAIL_PORT: Number(process.env.MAIL_PORT),
44 | MAIL_POOL: process.env.MAIL_POOL,
45 | MAIL_SECURE: process.env.MAIL_SECURE,
46 | MAIL_USER: process.env.MAIL_USER,
47 | MAIL_PASS: process.env.MAIL_PASS,
48 | MAIL_SENDER: process.env.MAIL_SENDER,
49 | MAIL_ADDRESS: process.env.MAIL_ADDRESS
50 | };
51 |
--------------------------------------------------------------------------------
/packages/funpi/apis/dict/categorySelectPage.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {
10 | page: fnSchema('page'),
11 | limit: fnSchema('limit'),
12 | keyword: fnSchema('keyword')
13 | },
14 | required: []
15 | },
16 | // 执行函数
17 | apiHandler: async (req) => {
18 | try {
19 | const dictCategoryModel = fastify.mysql //
20 | .table('sys_dict_category')
21 | .modify(function (db) {
22 | if (req.body.keyword) {
23 | db.where('name', 'like', `%${req.body.keyword}%`);
24 | }
25 | });
26 |
27 | // 记录总数
28 | const { totalCount } = await dictCategoryModel.clone().selectCount();
29 |
30 | // 记录列表
31 | const rows = await dictCategoryModel
32 | //
33 | .clone()
34 | .orderBy('created_at', 'desc')
35 | .selectData(req.body.page, req.body.limit);
36 |
37 | return {
38 | ...httpConfig.SELECT_SUCCESS,
39 | data: {
40 | total: totalCount,
41 | rows: rows,
42 | page: req.body.page,
43 | limit: req.body.limit
44 | }
45 | };
46 | } catch (err) {
47 | fastify.log.error(err);
48 | return httpConfig.SELECT_FAIL;
49 | }
50 | }
51 | });
52 | };
53 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/mailSelectPage.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnField } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 | import { tableData } from '../../tables/mailLog.js';
4 |
5 | export default async (fastify) => {
6 | fnRoute(import.meta.url, fastify, {
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | page: fnSchema('page'),
12 | limit: fnSchema('limit')
13 | },
14 | required: []
15 | },
16 | // 执行函数
17 | apiHandler: async (req) => {
18 | try {
19 | const mailLogModel = fastify.mysql //
20 | .table('sys_mail_log')
21 | .modify(function (db) {
22 | if (req.body.keyword) {
23 | db.where('nickname', 'like', `%${req.body.keyword}%`);
24 | }
25 | });
26 |
27 | // 记录总数
28 | const { totalCount } = await mailLogModel.clone().selectCount();
29 |
30 | // 记录列表
31 | const rows = await mailLogModel
32 | //
33 | .clone()
34 | .orderBy('created_at', 'desc')
35 | .selectData(req.body.page, req.body.limit, fnField(tableData));
36 |
37 | return {
38 | ...httpConfig.SELECT_SUCCESS,
39 | data: {
40 | total: totalCount,
41 | rows: rows,
42 | page: req.body.page,
43 | limit: req.body.limit
44 | }
45 | };
46 | } catch (err) {
47 | fastify.log.error(err);
48 | return httpConfig.SELECT_FAIL;
49 | }
50 | }
51 | });
52 | };
53 |
--------------------------------------------------------------------------------
/packages/funpi/apis/dict/categoryDelete.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {
10 | id: fnSchema('id')
11 | },
12 | required: ['id']
13 | },
14 | // 执行函数
15 | apiHandler: async (req) => {
16 | try {
17 | const dictCategoryModel = fastify.mysql.table('sys_dict_category').where({ id: req.body.id });
18 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
19 |
20 | const dictModel = fastify.mysql.table('sys_dict');
21 |
22 | const dictCategoryData = await dictCategoryModel.clone().selectOne(['id']);
23 |
24 | if (!dictCategoryData?.id) {
25 | return httpConfig.NO_DATA;
26 | }
27 |
28 | const childrenDict = await dictModel.clone().where({ category_id: req.body.id }).selectOne(['id']);
29 | if (childrenDict?.id) {
30 | return {
31 | ...httpConfig.DELETE_FAIL,
32 | msg: '此分类下有字典数据,无法删除'
33 | };
34 | }
35 |
36 | const result = await dictCategoryModel.clone().deleteData();
37 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req)));
38 |
39 | return {
40 | ...httpConfig.DELETE_SUCCESS,
41 | data: result
42 | };
43 | } catch (err) {
44 | fastify.log.error(err);
45 | return httpConfig.DELETE_FAIL;
46 | }
47 | }
48 | });
49 | };
50 |
--------------------------------------------------------------------------------
/packages/funpi/bootstrap/xmlParse.js:
--------------------------------------------------------------------------------
1 | import fp from 'fastify-plugin';
2 | import { XMLParser, XMLValidator } from 'fast-xml-parser';
3 |
4 | async function plugin(fastify) {
5 | const opts = {
6 | contentType: ['text/xml', 'application/xml', 'application/rss+xml'],
7 | validate: false
8 | };
9 |
10 | function contentParser(req, payload, done) {
11 | const xmlParser = new XMLParser(opts);
12 | const parsingOpts = opts;
13 |
14 | let body = '';
15 | payload.on('error', errorListener);
16 | payload.on('data', dataListener);
17 | payload.on('end', endListener);
18 |
19 | function errorListener(err) {
20 | done(err);
21 | }
22 | function endListener() {
23 | if (parsingOpts.validate) {
24 | const result = XMLValidator.validate(body, parsingOpts);
25 | if (result.err) {
26 | const invalidFormat = new Error('Invalid Format: ' + result.err.msg);
27 | invalidFormat.statusCode = 400;
28 | payload.removeListener('error', errorListener);
29 | payload.removeListener('data', dataListener);
30 | payload.removeListener('end', endListener);
31 | done(invalidFormat);
32 | } else {
33 | handleParseXml(body);
34 | }
35 | } else {
36 | handleParseXml(body);
37 | }
38 | }
39 | function dataListener(data) {
40 | body = body + data;
41 | }
42 | function handleParseXml(body) {
43 | try {
44 | done(null, xmlParser.parse(body));
45 | } catch (err) {
46 | done(err);
47 | }
48 | }
49 | }
50 |
51 | fastify.addContentTypeParser(opts.contentType, contentParser);
52 | }
53 |
54 | export default fp(plugin, {
55 | name: 'funpiXmlParse'
56 | });
57 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/roleSelectPage.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 |
4 | export default async (fastify) => {
5 | fnRoute(import.meta.url, fastify, {
6 | // 请求参数约束
7 | schemaRequest: {
8 | type: 'object',
9 | properties: {
10 | page: fnSchema('page'),
11 | limit: fnSchema('limit'),
12 | keyword: fnSchema('keyword')
13 | },
14 | required: []
15 | },
16 | // 执行函数
17 | apiHandler: async (req) => {
18 | try {
19 | const roleModel = fastify.mysql //
20 | .table('sys_role')
21 | .where('code', '<>', 'dev')
22 | .modify((db) => {
23 | if (req.body.keyword) {
24 | db.whereLike('name', `${req.body.keyword}`);
25 | }
26 | });
27 |
28 | const { totalCount } = await roleModel.clone().selectCount();
29 | const rows = await roleModel
30 | //
31 | .clone()
32 | .orderBy([
33 | { column: 'sort', order: 'asc' },
34 | { column: 'created_at', order: 'desc' }
35 | ])
36 | .selectData(req.body.page, req.body.limit);
37 |
38 | return {
39 | ...httpConfig.SELECT_SUCCESS,
40 | data: {
41 | total: totalCount,
42 | rows: rows,
43 | page: req.body.page,
44 | limit: req.body.limit
45 | }
46 | };
47 | } catch (err) {
48 | fastify.log.error(err);
49 | return httpConfig.SELECT_FAIL;
50 | }
51 | }
52 | });
53 | };
54 |
--------------------------------------------------------------------------------
/packages/funpi/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "funpi",
3 | "version": "7.20.27",
4 | "description": "FunPi(放屁) - 像放屁一样简单又自然的Node.js接口开发框架",
5 | "type": "module",
6 | "private": false,
7 | "publishConfig": {
8 | "access": "public",
9 | "registry": "https://registry.npmjs.org"
10 | },
11 | "exports": {
12 | ".": "./funpi.js",
13 | "./scripts/*": "./scripts/*.js"
14 | },
15 | "keywords": [
16 | "fastify",
17 | "nodejs",
18 | "api"
19 | ],
20 | "files": [
21 | "apis/",
22 | "bootstrap/",
23 | "config/",
24 | "plugins/",
25 | "schema/",
26 | "scripts/",
27 | "tables/",
28 | "utils/",
29 | ".gitignore",
30 | ".npmrc",
31 | ".prettierignore",
32 | ".prettier",
33 | "funpi.js",
34 | "LICENSE",
35 | "package.json",
36 | "README.md"
37 | ],
38 | "engines": {
39 | "node": ">=20.6.0"
40 | },
41 | "author": "chensuiyi ",
42 | "repository": {
43 | "type": "git",
44 | "url": "https://github.com/chenbimo/funpi"
45 | },
46 | "homepage": "https://chensuiyi.me",
47 | "dependencies": {
48 | "@fastify/autoload": "^6.3.0",
49 | "@fastify/cors": "^11.0.1",
50 | "@fastify/jwt": "^9.1.0",
51 | "@fastify/multipart": "^9.0.3",
52 | "@fastify/static": "^8.1.1",
53 | "ajv": "^8.17.1",
54 | "bullmq": "^5.52.2",
55 | "es-toolkit": "^1.37.2",
56 | "fast-xml-parser": "^5.2.3",
57 | "fastify": "^5.3.3",
58 | "fastify-plugin": "^5.0.1",
59 | "knex": "^3.1.0",
60 | "mysql2": "^3.14.1",
61 | "nodemailer": "^7.0.3",
62 | "pathe": "^2.0.3",
63 | "picomatch": "^4.0.2",
64 | "redis": "^5.0.1",
65 | "safe-stable-stringify": "^2.5.0",
66 | "winston": "^3.17.0",
67 | "winston-daily-rotate-file": "^5.0.0"
68 | },
69 | "gitHead": "1c28c0de7c0af8aa4582c45ab2d98e66c597c7a1"
70 | }
71 |
--------------------------------------------------------------------------------
/packages/funpi/apis/dict/categoryInsert.js:
--------------------------------------------------------------------------------
1 | import { camelCase } from 'es-toolkit';
2 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog } from '../../utils/index.js';
3 | import { httpConfig } from '../../config/http.js';
4 | import { tableData } from '../../tables/dictCategory.js';
5 |
6 | export default async (fastify) => {
7 | fnRoute(import.meta.url, fastify, {
8 | // 请求参数约束
9 | schemaRequest: {
10 | type: 'object',
11 | properties: {
12 | code: fnSchema(tableData.code),
13 | name: fnSchema(tableData.name),
14 | describe: fnSchema(tableData.describe)
15 | },
16 | required: ['code', 'name']
17 | },
18 | // 执行函数
19 | apiHandler: async (req) => {
20 | try {
21 | const dictCategoryModel = fastify.mysql.table('sys_dict_category');
22 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
23 |
24 | const dictCategoryData = await dictCategoryModel
25 | .clone()
26 | .where({ code: camelCase(req.body.code) })
27 | .selectOne(['id']);
28 |
29 | if (dictCategoryData?.id) {
30 | return {
31 | ...httpConfig.INSERT_FAIL,
32 | msg: '当前编号已存在'
33 | };
34 | }
35 |
36 | const result = await dictCategoryModel.insertData({
37 | code: camelCase(req.body.code),
38 | name: req.body.name,
39 | describe: req.body.describe
40 | });
41 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req)));
42 |
43 | return {
44 | ...httpConfig.INSERT_SUCCESS,
45 | data: result
46 | };
47 | } catch (err) {
48 | fastify.log.error(err);
49 | return httpConfig.INSERT_FAIL;
50 | }
51 | }
52 | });
53 | };
54 |
--------------------------------------------------------------------------------
/packages/funpi/apis/dict/categoryUpdate.js:
--------------------------------------------------------------------------------
1 | import { camelCase } from 'es-toolkit';
2 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog } from '../../utils/index.js';
3 | import { httpConfig } from '../../config/http.js';
4 | import { tableData } from '../../tables/dictCategory.js';
5 |
6 | export default async (fastify) => {
7 | fnRoute(import.meta.url, fastify, {
8 | // 请求参数约束
9 | schemaRequest: {
10 | type: 'object',
11 | properties: {
12 | id: fnSchema('page'),
13 | code: fnSchema(tableData.code),
14 | name: fnSchema(tableData.name),
15 | describe: fnSchema(tableData.describe)
16 | },
17 | required: ['id', 'code']
18 | },
19 | // 执行函数
20 | apiHandler: async (req) => {
21 | try {
22 | const dictCategoryModel = fastify.mysql.table('sys_dict_category');
23 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
24 |
25 | const dictCategoryData = await dictCategoryModel
26 | .clone()
27 | .where({ code: camelCase(req.body.code) })
28 | .selectOne(['id']);
29 |
30 | if (dictCategoryData?.id && dictCategoryData?.id !== req.body.id) {
31 | return {
32 | ...httpConfig.FAIL,
33 | msg: '当前编号已存在'
34 | };
35 | }
36 |
37 | const result = await dictCategoryModel
38 | .clone()
39 | .where({ id: req.body.id })
40 | .updateData({
41 | code: camelCase(req.body.code),
42 | name: req.body.name,
43 | describe: req.body.describe
44 | });
45 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req)));
46 |
47 | return httpConfig.UPDATE_SUCCESS;
48 | } catch (err) {
49 | fastify.log.error(err);
50 | return httpConfig.UPDATE_FAIL;
51 | }
52 | }
53 | });
54 | };
55 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/adminInsert.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog, fnCryptoMD5, fnCryptoHmacMD5 } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 | import { tableData } from '../../tables/admin.js';
4 |
5 | export default async (fastify) => {
6 | fnRoute(import.meta.url, fastify, {
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | username: fnSchema(tableData.username),
12 | password: fnSchema(tableData.password),
13 | nickname: fnSchema(tableData.nickname),
14 | role: fnSchema(tableData.role)
15 | },
16 | required: ['username', 'password', 'nickname', 'role']
17 | },
18 | // 执行函数
19 | apiHandler: async (req) => {
20 | try {
21 | if (req.body.role === 'dev') {
22 | return {
23 | ...httpConfig.FAIL,
24 | msg: '不能增加开发管理员角色'
25 | };
26 | }
27 | const adminModel = fastify.mysql.table('sys_admin');
28 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
29 |
30 | const adminData = await adminModel.clone().where('username', req.body.username).selectOne(['id']);
31 | if (adminData?.id) {
32 | return {
33 | ...httpConfig.FAIL,
34 | msg: '管理员账号已存在'
35 | };
36 | }
37 |
38 | const result = await adminModel.clone().insertData({
39 | username: req.body.username,
40 | password: fnCryptoHmacMD5(fnCryptoMD5(req.body.password), process.env.MD5_SALT),
41 | nickname: req.body.nickname,
42 | role: req.body.role
43 | });
44 |
45 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req, ['password'])));
46 |
47 | return {
48 | ...httpConfig.INSERT_SUCCESS,
49 | data: result
50 | };
51 | } catch (err) {
52 | fastify.log.error(err);
53 | return httpConfig.INSERT_FAIL;
54 | }
55 | }
56 | });
57 | };
58 |
--------------------------------------------------------------------------------
/packages/funpi/apis/dict/dictSelectPage.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 | import { tableData } from '../../tables/dict.js';
4 |
5 | export default async (fastify) => {
6 | fnRoute(import.meta.url, fastify, {
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | page: fnSchema('page'),
12 | limit: fnSchema('limit'),
13 | keyword: fnSchema('keyword'),
14 | category_code: fnSchema(tableData.category_code)
15 | },
16 | required: ['category_code']
17 | },
18 | // 执行函数
19 | apiHandler: async (req) => {
20 | try {
21 | const dictModel = fastify.mysql //
22 | .table('sys_dict')
23 | .where('category_code', req.body.category_code)
24 | .modify(function (db) {
25 | if (req.body.keyword) {
26 | db.where('name', 'like', `%${req.body.keyword}%`);
27 | }
28 | });
29 |
30 | // 记录总数
31 | const { totalCount } = await dictModel.clone().selectCount();
32 |
33 | // 记录列表
34 | const rowsTemp = await dictModel
35 | //
36 | .clone()
37 | .orderBy('created_at', 'desc')
38 | .selectData(req.body.page, req.body.limit);
39 |
40 | // 处理数字符号强制转换为数字值
41 | const rows = rowsTemp?.map((item) => {
42 | if (item.symbol === 'number') {
43 | item.value = Number(item.value);
44 | }
45 | return item;
46 | });
47 |
48 | return {
49 | ...httpConfig.SELECT_SUCCESS,
50 | data: {
51 | total: totalCount,
52 | rows: rows,
53 | page: req.body.page,
54 | limit: req.body.limit
55 | }
56 | };
57 | } catch (err) {
58 | fastify.log.error(err);
59 | return httpConfig.SELECT_FAIL;
60 | }
61 | }
62 | });
63 | };
64 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/roleInsert.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 | import { tableData } from '../../tables/role.js';
4 |
5 | export default async (fastify) => {
6 | fnRoute(import.meta.url, fastify, {
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | code: fnSchema(tableData.code),
12 | name: fnSchema(tableData.name),
13 | sort: fnSchema(tableData.sort),
14 | describe: fnSchema(tableData.describe),
15 | menu_ids: fnSchema(tableData.menu_ids),
16 | api_ids: fnSchema(tableData.api_ids)
17 | },
18 | required: ['name', 'code']
19 | },
20 | // 执行函数
21 | apiHandler: async (req) => {
22 | try {
23 | const roleModel = fastify.mysql.table('sys_role');
24 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
25 |
26 | const roleData = await roleModel //
27 | .clone()
28 | .where('name', req.body.name)
29 | .orWhere('code', req.body.code)
30 | .selectOne(['id']);
31 |
32 | if (roleData?.id) {
33 | return {
34 | ...httpConfig.INSERT_FAIL,
35 | msg: '角色名称或编码已存在'
36 | };
37 | }
38 |
39 | const result = await roleModel.clone().insertData({
40 | code: req.body.code,
41 | name: req.body.name,
42 | sort: req.body.sort,
43 | describe: req.body.describe,
44 | menu_ids: req.body.menu_ids,
45 | api_ids: req.body.api_ids
46 | });
47 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req)));
48 |
49 | await fastify.cacheRoleData();
50 |
51 | return {
52 | ...httpConfig.INSERT_SUCCESS,
53 | data: result
54 | };
55 | } catch (err) {
56 | fastify.log.error(err);
57 | return httpConfig.INSERT_FAIL;
58 | }
59 | }
60 | });
61 | };
62 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/adminUpdate.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog, fnCryptoHmacMD5 } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 | import { tableData } from '../../tables/admin.js';
4 |
5 | export default async (fastify) => {
6 | fnRoute(import.meta.url, fastify, {
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | id: fnSchema('id'),
12 | username: fnSchema(tableData.username),
13 | password: fnSchema(tableData.password),
14 | nickname: fnSchema(tableData.nickname),
15 | role: fnSchema(tableData.role)
16 | },
17 | required: ['id']
18 | },
19 | // 执行函数
20 | apiHandler: async (req) => {
21 | try {
22 | if (req.body.role === 'dev') {
23 | return {
24 | ...httpConfig.FAIL,
25 | msg: '不能增加开发管理员角色'
26 | };
27 | }
28 | const adminModel = fastify.mysql.table('sys_admin');
29 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
30 |
31 | const adminData = await adminModel.clone().where('username', req.body.username).selectOne(['id']);
32 | if (adminData?.id && adminData?.id !== req.body.id) {
33 | return {
34 | ...httpConfig.FAIL,
35 | msg: '管理员账号已存在'
36 | };
37 | }
38 | const updateData = {
39 | nickname: req.body.nickname,
40 | username: req.body.username,
41 | role: req.body.role
42 | };
43 |
44 | if (req.body.password) {
45 | updateData.password = fnCryptoHmacMD5(req.body.password, process.env.MD5_SALT);
46 | }
47 | await adminModel.clone().where({ id: req.body.id }).updateData(updateData);
48 |
49 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req, ['password'])));
50 |
51 | return httpConfig.UPDATE_SUCCESS;
52 | } catch (err) {
53 | fastify.log.error(err);
54 | return httpConfig.UPDATE_FAIL;
55 | }
56 | }
57 | });
58 | };
59 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/roleUpdate.js:
--------------------------------------------------------------------------------
1 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog } from '../../utils/index.js';
2 | import { httpConfig } from '../../config/http.js';
3 | import { tableData } from '../../tables/role.js';
4 |
5 | export default async (fastify) => {
6 | fnRoute(import.meta.url, fastify, {
7 | // 请求参数约束
8 | schemaRequest: {
9 | type: 'object',
10 | properties: {
11 | id: fnSchema('id'),
12 | code: fnSchema(tableData.code),
13 | name: fnSchema(tableData.name),
14 | sort: fnSchema(tableData.sort),
15 | describe: fnSchema(tableData.describe),
16 | menu_ids: fnSchema(tableData.menu_ids),
17 | api_ids: fnSchema(tableData.api_ids)
18 | },
19 | required: ['id']
20 | },
21 | // 执行函数
22 | apiHandler: async (req) => {
23 | try {
24 | const roleModel = fastify.mysql.table('sys_role');
25 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
26 |
27 | const roleData = await roleModel //
28 | .clone()
29 | .where('name', req.body.name)
30 | .orWhere('code', req.body.code)
31 | .selectOne(['id']);
32 |
33 | // 编码存在且 id 不等于当前角色
34 | if (roleData?.id && roleData?.id !== req.body.id) {
35 | return {
36 | ...httpConfig.INSERT_FAIL,
37 | msg: '角色名称或编码已存在'
38 | };
39 | }
40 |
41 | const result = await roleModel.clone().where({ id: req.body.id }).updateData({
42 | code: req.body.code,
43 | name: req.body.name,
44 | sort: req.body.sort,
45 | describe: req.body.describe,
46 | menu_ids: req.body.menu_ids,
47 | api_ids: req.body.api_ids
48 | });
49 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req)));
50 |
51 | await fastify.cacheRoleData();
52 |
53 | return {
54 | ...httpConfig.UPDATE_SUCCESS,
55 | data: result
56 | };
57 | } catch (err) {
58 | fastify.log.error(err);
59 | return httpConfig.UPDATE_FAIL;
60 | }
61 | }
62 | });
63 | };
64 |
--------------------------------------------------------------------------------
/packages/funpi/apis/dict/dictInsert.js:
--------------------------------------------------------------------------------
1 | import { camelCase } from 'es-toolkit';
2 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog } from '../../utils/index.js';
3 | import { httpConfig } from '../../config/http.js';
4 | import { tableData } from '../../tables/dict.js';
5 |
6 | export default async (fastify) => {
7 | fnRoute(import.meta.url, fastify, {
8 | // 请求参数约束
9 | schemaRequest: {
10 | type: 'object',
11 | properties: {
12 | category_id: fnSchema(tableData.category_id),
13 | category_code: fnSchema(tableData.category_code),
14 | code: fnSchema(tableData.code),
15 | name: fnSchema(tableData.name),
16 | value: fnSchema(tableData.value),
17 | symbol: fnSchema(tableData.symbol),
18 | thumbnail: fnSchema(tableData.thumbnail),
19 | describe: fnSchema(tableData.describe)
20 | },
21 | required: ['category_id', 'category_code', 'code', 'name', 'value', 'symbol']
22 | },
23 | // 执行函数
24 | apiHandler: async (req) => {
25 | try {
26 | // 如果传的值是数值类型,则判断是否为有效数值
27 | if (req.body.symbol === 'number') {
28 | if (Number.isNaN(Number(req.body.value)) === true) {
29 | return {
30 | ...httpConfig.INSERT_FAIL,
31 | msg: '字典值不是一个数字类型'
32 | };
33 | }
34 | }
35 |
36 | const dictModel = fastify.mysql.table('sys_dict');
37 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
38 |
39 | const result = await dictModel.insertData({
40 | category_id: req.body.category_id,
41 | category_code: camelCase(req.body.category_code),
42 | code: camelCase(req.body.code),
43 | name: req.body.name,
44 | value: req.body.value,
45 | symbol: req.body.symbol,
46 | thumbnail: req.body.thumbnail,
47 | describe: req.body.describe
48 | });
49 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req)));
50 |
51 | return {
52 | ...httpConfig.INSERT_SUCCESS,
53 | data: result
54 | };
55 | } catch (err) {
56 | fastify.log.error(err);
57 | return httpConfig.INSERT_FAIL;
58 | }
59 | }
60 | });
61 | };
62 |
--------------------------------------------------------------------------------
/packages/funpi/apis/dict/dictUpdate.js:
--------------------------------------------------------------------------------
1 | import { camelCase } from 'es-toolkit';
2 | import { fnRoute, fnSchema, fnDataClear, fnRequestLog } from '../../utils/index.js';
3 | import { httpConfig } from '../../config/http.js';
4 | import { tableData } from '../../tables/dict.js';
5 |
6 | export default async (fastify) => {
7 | fnRoute(import.meta.url, fastify, {
8 | // 请求参数约束
9 | schemaRequest: {
10 | type: 'object',
11 | properties: {
12 | id: fnSchema('id'),
13 | category_id: fnSchema(tableData.category_id),
14 | category_code: fnSchema(tableData.category_code),
15 | code: fnSchema(tableData.code),
16 | name: fnSchema(tableData.name),
17 | value: fnSchema(tableData.value),
18 | symbol: fnSchema(tableData.symbol),
19 | thumbnail: fnSchema(tableData.thumbnail),
20 | describe: fnSchema(tableData.describe)
21 | },
22 | required: ['id']
23 | },
24 | // 执行函数
25 | apiHandler: async (req) => {
26 | try {
27 | if (req.body.type === 'number') {
28 | if (Number.isNaN(Number(req.body.value)) === true) {
29 | return {
30 | ...httpConfig.UPDATE_FAIL,
31 | msg: '字典值不是一个数字类型'
32 | };
33 | }
34 | }
35 | const dictModel = fastify.mysql.table('sys_dict').modify(function (db) {});
36 | const adminActionLogModel = fastify.mysql.table('sys_admin_action_log');
37 |
38 | const result = await dictModel
39 | .clone()
40 | .where({ id: req.body.id })
41 | .updateData({
42 | category_id: req.body.category_id,
43 | category_code: camelCase(req.body.category_code),
44 | code: camelCase(req.body.code),
45 | name: req.body.name,
46 | value: req.body.value,
47 | symbol: req.body.symbol,
48 | thumbnail: req.body.thumbnail,
49 | describe: req.body.describe
50 | });
51 | await adminActionLogModel.clone().insertData(fnDataClear(fnRequestLog(req)));
52 |
53 | return httpConfig.UPDATE_SUCCESS;
54 | } catch (err) {
55 | fastify.log.error(err);
56 | return httpConfig.UPDATE_FAIL;
57 | }
58 | }
59 | });
60 | };
61 |
--------------------------------------------------------------------------------
/packages/funpi/apis/admin/adminLogin.js:
--------------------------------------------------------------------------------
1 | import { omit as es_omit } from 'es-toolkit';
2 | import { fnRoute, fnSchema, fnCryptoHmacMD5 } from '../../utils/index.js';
3 | import { httpConfig } from '../../config/http.js';
4 | import { tableData } from '../../tables/admin.js';
5 |
6 | export default async (fastify) => {
7 | fnRoute(import.meta.url, fastify, {
8 | // 请求参数约束
9 | schemaRequest: {
10 | type: 'object',
11 | properties: {
12 | account: fnSchema({ name: '账号', type: 'string', min: 1, max: 100 }),
13 | password: fnSchema(tableData.password)
14 | },
15 | required: ['account', 'password']
16 | },
17 | // 执行函数
18 | apiHandler: async (req) => {
19 | try {
20 | const adminModel = fastify.mysql.table('sys_admin');
21 | const adminLoginLogModel = fastify.mysql.table('sys_admin_login_log');
22 |
23 | // 查询管理员是否存在
24 | // TODO: 增加邮箱注册和邮箱登录
25 | const adminData = await adminModel //
26 | .clone()
27 | .orWhere({ username: req.body.account })
28 | .selectOne(['id', 'password', 'username', 'nickname', 'role']);
29 |
30 | // 判断用户存在
31 | if (!adminData?.id) {
32 | return {
33 | ...httpConfig.FAIL,
34 | msg: '用户不存在'
35 | };
36 | }
37 |
38 | // 判断密码
39 | if (fnCryptoHmacMD5(req.body.password, process.env.MD5_SALT) !== adminData.password) {
40 | return {
41 | ...httpConfig.FAIL,
42 | msg: '密码错误'
43 | };
44 | }
45 | // 记录登录日志
46 | await adminLoginLogModel.clone().insertData({
47 | user_id: adminData.id,
48 | username: adminData.username,
49 | nickname: adminData.nickname,
50 | role: adminData.role,
51 | ip: req.ip || '',
52 | ua: req.headers['user-agent'] || ''
53 | });
54 |
55 | // 成功返回
56 | return {
57 | ...httpConfig.SUCCESS,
58 | msg: '登录成功',
59 | data: es_omit(adminData, ['password']),
60 | token: await fastify.jwt.sign({
61 | id: adminData.id,
62 | username: adminData.username,
63 | nickname: adminData.nickname,
64 | role_type: 'admin',
65 | role: adminData.role
66 | })
67 | };
68 | } catch (err) {
69 | fastify.log.error(err);
70 | return {
71 | ...httpConfig.FAIL,
72 | msg: '登录失败'
73 | };
74 | }
75 | }
76 | });
77 | };
78 |
--------------------------------------------------------------------------------
/packages/funpi/utils/colors.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov
2 |
3 | let p = process || {},
4 | argv = p.argv || [],
5 | env = p.env || {};
6 | let isColorSupported = !(!!env.NO_COLOR || argv.includes('--no-color')) && (!!env.FORCE_COLOR || argv.includes('--color') || p.platform === 'win32' || ((p.stdout || {}).isTTY && env.TERM !== 'dumb') || !!env.CI);
7 |
8 | let formatter =
9 | (open, close, replace = open) =>
10 | (input) => {
11 | let string = '' + input,
12 | index = string.indexOf(close, open.length);
13 | return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
14 | };
15 |
16 | let replaceClose = (string, close, replace, index) => {
17 | let result = '',
18 | cursor = 0;
19 | do {
20 | result += string.substring(cursor, index) + replace;
21 | cursor = index + close.length;
22 | index = string.indexOf(close, cursor);
23 | } while (~index);
24 | return result + string.substring(cursor);
25 | };
26 |
27 | let createColors = (enabled = isColorSupported) => {
28 | let f = enabled ? formatter : () => String;
29 | return {
30 | // 符号
31 |
32 | isColorSupported: enabled,
33 | reset: f('\x1b[0m', '\x1b[0m'),
34 | bold: f('\x1b[1m', '\x1b[22m', '\x1b[22m\x1b[1m'),
35 | dim: f('\x1b[2m', '\x1b[22m', '\x1b[22m\x1b[2m'),
36 | italic: f('\x1b[3m', '\x1b[23m'),
37 | underline: f('\x1b[4m', '\x1b[24m'),
38 | inverse: f('\x1b[7m', '\x1b[27m'),
39 | hidden: f('\x1b[8m', '\x1b[28m'),
40 | strikethrough: f('\x1b[9m', '\x1b[29m'),
41 |
42 | black: f('\x1b[30m', '\x1b[39m'),
43 | red: f('\x1b[31m', '\x1b[39m'),
44 | green: f('\x1b[32m', '\x1b[39m'),
45 | yellow: f('\x1b[33m', '\x1b[39m'),
46 | blue: f('\x1b[34m', '\x1b[39m'),
47 | magenta: f('\x1b[35m', '\x1b[39m'),
48 | cyan: f('\x1b[36m', '\x1b[39m'),
49 | white: f('\x1b[37m', '\x1b[39m'),
50 | gray: f('\x1b[90m', '\x1b[39m'),
51 |
52 | bgBlack: f('\x1b[40m', '\x1b[49m'),
53 | bgRed: f('\x1b[41m', '\x1b[49m'),
54 | bgGreen: f('\x1b[42m', '\x1b[49m'),
55 | bgYellow: f('\x1b[43m', '\x1b[49m'),
56 | bgBlue: f('\x1b[44m', '\x1b[49m'),
57 | bgMagenta: f('\x1b[45m', '\x1b[49m'),
58 | bgCyan: f('\x1b[46m', '\x1b[49m'),
59 | bgWhite: f('\x1b[47m', '\x1b[49m'),
60 |
61 | blackBright: f('\x1b[90m', '\x1b[39m'),
62 | redBright: f('\x1b[91m', '\x1b[39m'),
63 | greenBright: f('\x1b[92m', '\x1b[39m'),
64 | yellowBright: f('\x1b[93m', '\x1b[39m'),
65 | blueBright: f('\x1b[94m', '\x1b[39m'),
66 | magentaBright: f('\x1b[95m', '\x1b[39m'),
67 | cyanBright: f('\x1b[96m', '\x1b[39m'),
68 | whiteBright: f('\x1b[97m', '\x1b[39m'),
69 |
70 | bgBlackBright: f('\x1b[100m', '\x1b[49m'),
71 | bgRedBright: f('\x1b[101m', '\x1b[49m'),
72 | bgGreenBright: f('\x1b[102m', '\x1b[49m'),
73 | bgYellowBright: f('\x1b[103m', '\x1b[49m'),
74 | bgBlueBright: f('\x1b[104m', '\x1b[49m'),
75 | bgMagentaBright: f('\x1b[105m', '\x1b[49m'),
76 | bgCyanBright: f('\x1b[106m', '\x1b[49m'),
77 | bgWhiteBright: f('\x1b[107m', '\x1b[49m')
78 | };
79 | };
80 |
81 | const colors = createColors();
82 |
83 | export { colors };
84 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/api/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
接口总数
7 |
{{ $Data.apiTotal }}个
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
94 |
95 |
99 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/dictCategory/components/editDataDrawer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 添加字典分类
5 | 编辑字典分类
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
27 |
28 |
29 |
30 |
110 |
--------------------------------------------------------------------------------
/packages/admin/src/styles/internal.scss:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | border: 0;
4 | margin: 0;
5 | outline: 0;
6 | box-sizing: border-box;
7 | user-select: none;
8 | -webkit-appearance: none;
9 | }
10 | // ::-webkit-scrollbar {
11 | // width: 10px;
12 | // height: 10px;
13 | // }
14 | // ::-webkit-scrollbar-track {
15 | // border-radius: 5px;
16 | // }
17 | // ::-webkit-scrollbar-track-piece {
18 | // border-radius: 5px;
19 | // }
20 | // ::-webkit-scrollbar-thumb {
21 | // border-radius: 5px;
22 | // border: 3px solid transparent;
23 | // background-color: rgba(190, 190, 190, 0.4);
24 | // background-clip: padding-box;
25 | // }
26 |
27 | html,
28 | body {
29 | font-size: 14px;
30 | font-family: -apple-system, system-ui, 'Segoe UI', 'Roboto', 'Ubuntu', 'Cantarell', 'Noto Sans', sans-serif, 'BlinkMacSystemFont', 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial !important;
31 | }
32 |
33 | a {
34 | color: inherit;
35 | text-decoration: none;
36 | display: inline-block;
37 | }
38 |
39 | .app {
40 | position: fixed;
41 | top: 0;
42 | right: 0;
43 | bottom: 0;
44 | left: 0;
45 | }
46 |
47 | .my-modal-class {
48 | .bodyer {
49 | max-height: 70vh;
50 | overflow-y: auto;
51 | overflow-x: hidden;
52 | }
53 | }
54 |
55 | .menu-area {
56 | .arco-menu .arco-icon {
57 | margin-right: 0 !important;
58 | }
59 | }
60 |
61 | .link {
62 | color: #2588ff;
63 | }
64 |
65 | .bg-contain,
66 | .bg-cover {
67 | background-repeat: no-repeat;
68 | background-position: center center;
69 | }
70 | .bg-contain {
71 | background-size: contain;
72 | }
73 | .bg-cover {
74 | background-size: cover;
75 | }
76 |
77 | .bodyer-modal {
78 | max-height: 60vh;
79 | overflow-y: auto;
80 | overflow-x: hidden;
81 | }
82 |
83 | .common-badge {
84 | display: flex;
85 | height: 24px;
86 | line-height: 24px;
87 | background-color: #eee;
88 | border-radius: 2px;
89 | overflow: hidden;
90 | font-size: 14px;
91 | .label {
92 | padding: 0 8px;
93 | background-color: #165dff;
94 | color: #fff;
95 | }
96 | .value {
97 | padding: 0 8px;
98 | }
99 | }
100 |
101 | .page-full {
102 | position: absolute;
103 | top: 15px;
104 | right: 15px;
105 | bottom: 10px;
106 | left: 15px;
107 | .page-action {
108 | position: absolute;
109 | top: 0;
110 | left: 0;
111 | right: 0;
112 | height: 34px;
113 | display: flex;
114 | justify-content: space-between;
115 | .left {
116 | display: flex;
117 | align-items: center;
118 | }
119 | .right {
120 | display: flex;
121 | align-items: center;
122 | }
123 | }
124 | .page-table {
125 | position: absolute;
126 | top: 34px;
127 | left: 0;
128 | right: 0;
129 | bottom: 30px;
130 | padding: 10px 0;
131 | &.no-action {
132 | top: 0px;
133 | }
134 | &.no-page {
135 | bottom: 0px;
136 | }
137 | }
138 | .page-page {
139 | position: absolute;
140 | bottom: 0;
141 | left: 0;
142 | right: 0;
143 | height: 30px;
144 | display: flex;
145 | justify-content: space-between;
146 | }
147 | }
148 |
149 | .delete-modal-class {
150 | .arco-modal-body {
151 | text-align: center;
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/adminLoginLog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
29 |
30 |
31 |
32 |
100 |
101 |
105 |
--------------------------------------------------------------------------------
/packages/admin/src/utils/internal.js:
--------------------------------------------------------------------------------
1 | import { format, formatDistanceToNow } from 'date-fns';
2 | import { zhCN } from 'date-fns/locale';
3 |
4 | // 获取资源
5 | export function utilInternalAssets(name) {
6 | return new URL(`../assets/${name}`, import.meta.url).href;
7 | }
8 |
9 | // 树结构遍历
10 | export const utilTreeTraverse = (tree, mapFunction) => {
11 | function preorder(node, index, parent) {
12 | const newNode = Object.assign({}, mapFunction(node, index, parent));
13 |
14 | if ('children' in node) {
15 | newNode.children = node.children.map(function (child, index) {
16 | return preorder(child, index, node);
17 | });
18 | }
19 |
20 | return newNode;
21 | }
22 |
23 | return preorder(tree, null, null);
24 | };
25 |
26 | export const utilArrayToTree = (arrs, id = 'id', pid = 'pid', children = 'children', forceChildren = true) => {
27 | // 输入验证
28 | if (!Array.isArray(arrs) || arrs.length === 0) {
29 | return [];
30 | }
31 |
32 | // 使用 Map 存储项目,并创建副本避免修改原始数据
33 | const idMap = new Map();
34 | arrs.forEach((item) => {
35 | // 创建每个项目的副本
36 | idMap.set(item[id], { ...item });
37 | });
38 |
39 | const treeData = [];
40 |
41 | // 构建树结构
42 | idMap.forEach((item) => {
43 | const parentId = item[pid];
44 | const parent = idMap.get(parentId);
45 |
46 | if (parent) {
47 | // 添加到父项的子列表
48 | if (!parent[children]) {
49 | parent[children] = [];
50 | }
51 | parent[children].push(item);
52 | } else {
53 | // 根节点
54 | if (forceChildren && !item[children]) {
55 | item[children] = [];
56 | }
57 | treeData.push(item);
58 | }
59 | });
60 |
61 | return treeData;
62 | };
63 |
64 | /**
65 | * 转换相对时间
66 | * @alias yd_datetime_relativeTime
67 | * @category datetime
68 | * @param {Array | object} data 数组或对象
69 | * @returns {object} 返回转换后的相对时间
70 | * @author 陈随易
71 | * @example yd_datetime_relativeTime([])
72 | */
73 | export const utilRelativeTime = (data) => {
74 | // 转换相对时间
75 | const _convertTime = (obj) => {
76 | try {
77 | const item = {};
78 | for (let key in obj) {
79 | if (Object.prototype.hasOwnProperty.call(obj, key)) {
80 | const value = obj[key];
81 | if (key.endsWith('_at')) {
82 | let key1 = key.replace('_at', '_at1');
83 | let key2 = key.replace('_at', '_at2');
84 | let dt = new Date(value);
85 | if (value !== 0) {
86 | item[key] = value;
87 | item[key1] = format(dt, 'yyyy-MM-dd HH:mm:ss');
88 | item[key2] = formatDistanceToNow(dt, { locale: zhCN, addSuffix: true });
89 | } else {
90 | item[key] = '';
91 | }
92 | } else {
93 | item[key] = value;
94 | }
95 | }
96 | }
97 |
98 | return item;
99 | } catch (err) {
100 | console.log('🚀 ~ err:', err);
101 | }
102 | };
103 | // 如果是数组
104 | if (Array.isArray(data)) {
105 | return data.map((item) => {
106 | return _convertTime(item);
107 | });
108 | }
109 |
110 | // 如果是对象
111 | return _convertTime(data);
112 | };
113 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/menu/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
29 |
30 |
31 |
100 |
101 |
105 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/adminActionLog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
96 |
97 |
101 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/mailLog/components/sendMailDrawer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 发送邮件
4 |
5 |
6 |
7 |
8 | 普通邮件
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
29 |
30 |
31 |
32 |
105 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # funpi 是什么?
2 |
3 | 中文名称 `放屁` 接口框架。
4 |
5 | 像放屁一样简单又自然的 `Node.js` 接口开发框架。
6 |
7 | > 注意:本项目 `v7` 为内测版,`v8` 才是公测版,谨慎使用。
8 | >
9 | > 自 `v7.15.0` 版本开始,本项目仅支持 [Bun](https://bun.sh),不再支持 `Node.js`。
10 |
11 | ### 仓库地址
12 |
13 | [github - https://github.com/chenbimo/funpi](https://github.com/chenbimo/funpi)
14 |
15 | ### 使用教程
16 |
17 | [funpi(放屁)使用文档](https://sourl.cn/bUq25t)
18 |
19 | ### 作者介绍
20 |
21 | [前端之虎陈随易 https://chensuiyi.me](https://chensuiyi.me)
22 |
23 | ### 演示地址
24 |
25 | - [https://funpi-demo.yicode.tech](https://funpi-demo.yicode.tech)
26 |
27 | ### 功能特点
28 |
29 | - ✅ 只需 `简单配置`,即可快速上手开发。
30 | - ✅ 自动生成 `接口文档`,方便前后端对接。
31 | - ✅ 自带 `权限`、`角色`、`管理`、`日志`、`菜单`、`接口`、`字典` 等基础功能。
32 | - ✅ 自带 `邮件发送`,`文件上传` 等功能。
33 | - ✅ 自带 `日志打印` 和 `日志分割` 功能。
34 | - ✅ 自带 `jwt` 鉴权机制。
35 | - ✅ 自带 `登录日志`,`邮件日志` 等功能。
36 | - ✅ 自带配套的后台管理系统 `yiadmin`,30 分钟搭建一个后台管理系统。
37 | - ✅ 默认已处理 `跨域` 问题,无需再为跨域担心。
38 | - ✅ 优先使用 `缓存`,提高应用性能。
39 | - ✅ 默认提供 `静态文件托管` 功能。
40 | - ✅ 可以 `一键更新` 后台管理系统。
41 | - ✅ 全面的 `接口参数验证` 功能,极大减少安全隐患。
42 | - ✅ 提供 `数据库表字段设计` 和 `表结构同步` 功能。
43 |
44 | ### 功能限制
45 |
46 | 本框架做了很多约束,减少自由度,增加确定度,稳定度。
47 |
48 | - ❎ 仅支持 `Bun`,不支持 `Node.js`,`Deno` 等。
49 | - ❎ 仅支持 `单机部署`,使用 `pm2` 管理。
50 | - ❎ 仅支持 `单角色权限`。
51 | - ❎ 仅支持 `Mysql` 关系数据库。
52 | - ❎ 仅支持 `Redis` 缓存数据库。
53 | - ❎ 仅支持 `POST` 和 `GET` 请求方法。
54 | - ❎ 仅支持 `整数`、`浮点数`、`文本`、`字符串` 这四种数据库字段类型。
55 | - ❎ 不支持 `分库分表`。
56 | - ❎ 不支持 `Docker` 部署,请自行研究。
57 | - ❎ 不支持 `分布式部署`。
58 | - ❎ 不支持 `Restful` 规范,不认同 `Restful` 规范,不使用 `Restful` 规范。
59 |
60 | ### 付费插件
61 |
62 | - `微信扫码插件`,登录注册,需要提供微信公众号。
63 | - `在线人数统计插件`,提供 `踢人`,`拉黑` 等功能。
64 | - `微信支付插件`,支持 `多产品`、`折扣`、`优惠` 等功能。
65 |
66 | ### 注意事项
67 |
68 | - 与本项目逻辑、BUG、建议相关的问题,请联系作者无偿 `免费处理`。
69 | - 与本项目无关的业务、功能、需求、部署相关的问题,请联系作者 `有偿咨询`。
70 |
71 | ### 实际效果
72 |
73 | 使用 `funpi` + `yiadmin` 驱动的,免费且开源的后台管理系统。
74 |
75 | #### 📄 登录页面
76 |
77 | 
78 |
79 | #### 📄 菜单页面
80 |
81 | 
82 |
83 | #### 📄 接口页面
84 |
85 | 
86 |
87 | #### 📄 角色页面
88 |
89 | 
90 |
91 | #### 📄 登录日志
92 |
93 | 
94 |
95 | #### 📄 邮件日志
96 |
97 | 
98 |
99 | ### 版权说明
100 |
101 | `funpi(放屁)` 使用 `Apache 2.0` 协议开源
102 |
103 | > 一句话总结:开源不等于放弃版权,不可侵犯原作者版权,改动处要做说明,可以闭源使用。
104 |
105 | 拥有版权(Copyright)意味着你对你开发的软件及其源代码拥有著作权,所有权和其他法定权利,使用一个开源协议并不意味着放弃版权。
106 |
107 | 在 `Apache 2.0` 协议许可下,您可以:
108 |
109 | - **商业化使用**(这意味着,您可以出于商业目的使用这些源代码)
110 | - **再分发**(这意味着,您可以将源代码副本传输给其他任何人)
111 | - **修改**(这意味着,您可以修改源代码)
112 | - **专利使用**(这意味着,版权人明确声明授予您专利使用权)
113 | - **私人使用**(这意味着,您可以出于一切目的私下使用和修改源代码)
114 |
115 | 唯须遵守以下条款:
116 |
117 | - **协议和版权通知**(这意味着,软件中必须包含许可证和版权声明的副本)
118 | - **状态更改说明**(如果您更改软件,您应当提供适当的说明)
119 |
120 | 除此之外,该软件:
121 |
122 | - **提供责任限制**(版权人声明不对使用者造成的任何损失负责)
123 | - **限制商标使用** (不能使用版权人的商标)
124 | - **不提供任何担保**(版权人声明不为该软件的品质提供任何担保)
125 |
126 | 进一步说明:
127 |
128 | 1. 本软件又叫本 **作品**,可以是源码,也可以是编译或转换后的其他形式。**衍生作品** 是在本作品的基础上修改后的有原创性的工作成果。本作品的 **贡献者** 包括许可人和其他提交了贡献的人,以下统称 **我**。
129 | 2. 我授予你权利:你可以免费复制、使用、修改、再许可、分发本作品及衍生作品(可以不用公开源码)。
130 | 3. 如果本软件涉及我的专利(或潜在专利),我在此授予你专利许可,你可以永久性地免费使用此专利,用于制作、使用、出售、转让本作品。如果你哪天居然告本作品侵权,你的专利许可在你告我那天被收回。
131 | 4. 你在复制和分发本作品或衍生作品时,要满足以下条件。
132 |
133 | - 带一份本许可证。
134 | - 如果你修改了什么,要在改动的文件中有明显的修改声明。
135 | - 如果你以源码形式分发,你必须保留本作品的版权、专利、商标和归属声明。
136 | - 如果本作品带了 **NOTICE** 文件,你就得带上 **NOTICE** 文件中包含的归属声明。即便你的发布是不带源码的,你也得带上此文件,并在作品某处予以展示。
137 | - 你可以对自己的修改添加版权说明。对于你的修改或者整个衍生作品,你可以使用不同的许可,但你对本作品的使用、复制和分发等,必须符合本许可证规定。
138 |
139 | 5. 你提交贡献就表明你默认遵守本许可的条款和条件。当然,你可以和我签订另外的专门的条款。
140 | 6. 你不许使用我的商品名、商标、服务标志或产品名。
141 | 7. 本作品是 **按原样**(AS IS)提供的,没有任何保证啊,你懂的。
142 | 8. 我可不负任何责任。除非我书面同意,或者法律有这样的要求(例如对故意和重大过失行为负责)。
143 | 9. 你可以向别人提供保证,你可以向别人收费,但那都是你的事,别给我惹麻烦。
144 |
145 | 注意以上的 **我**,既包含了许可人,也包含了每位 **贡献者**。
146 |
--------------------------------------------------------------------------------
/packages/funpi/README.md:
--------------------------------------------------------------------------------
1 | # funpi 是什么?
2 |
3 | 中文名称 `放屁` 接口框架。
4 |
5 | 像放屁一样简单又自然的 `Node.js` 接口开发框架。
6 |
7 | > 注意:本项目 `v7` 为内测版,`v8` 才是公测版,谨慎使用。
8 | >
9 | > 自 `v7.15.0` 版本开始,本项目仅支持 [Bun](https://bun.sh),不再支持 `Node.js`。
10 |
11 | ### 仓库地址
12 |
13 | [github - https://github.com/chenbimo/funpi](https://github.com/chenbimo/funpi)
14 |
15 | ### 使用教程
16 |
17 | [funpi(放屁)使用文档](https://sourl.cn/bUq25t)
18 |
19 | ### 作者介绍
20 |
21 | [前端之虎陈随易 https://chensuiyi.me](https://chensuiyi.me)
22 |
23 | ### 演示地址
24 |
25 | - [https://funpi-demo.yicode.tech](https://funpi-demo.yicode.tech)
26 |
27 | ### 功能特点
28 |
29 | - ✅ 只需 `简单配置`,即可快速上手开发。
30 | - ✅ 自动生成 `接口文档`,方便前后端对接。
31 | - ✅ 自带 `权限`、`角色`、`管理`、`日志`、`菜单`、`接口`、`字典` 等基础功能。
32 | - ✅ 自带 `邮件发送`,`文件上传` 等功能。
33 | - ✅ 自带 `日志打印` 和 `日志分割` 功能。
34 | - ✅ 自带 `jwt` 鉴权机制。
35 | - ✅ 自带 `登录日志`,`邮件日志` 等功能。
36 | - ✅ 自带配套的后台管理系统 `yiadmin`,30 分钟搭建一个后台管理系统。
37 | - ✅ 默认已处理 `跨域` 问题,无需再为跨域担心。
38 | - ✅ 优先使用 `缓存`,提高应用性能。
39 | - ✅ 默认提供 `静态文件托管` 功能。
40 | - ✅ 可以 `一键更新` 后台管理系统。
41 | - ✅ 全面的 `接口参数验证` 功能,极大减少安全隐患。
42 | - ✅ 提供 `数据库表字段设计` 和 `表结构同步` 功能。
43 |
44 | ### 功能限制
45 |
46 | 本框架做了很多约束,减少自由度,增加确定度,稳定度。
47 |
48 | - ❎ 仅支持 `Bun`,不支持 `Node.js`,`Deno` 等。
49 | - ❎ 仅支持 `单机部署`,使用 `pm2` 管理。
50 | - ❎ 仅支持 `单角色权限`。
51 | - ❎ 仅支持 `Mysql` 关系数据库。
52 | - ❎ 仅支持 `Redis` 缓存数据库。
53 | - ❎ 仅支持 `POST` 和 `GET` 请求方法。
54 | - ❎ 仅支持 `整数`、`浮点数`、`文本`、`字符串` 这四种数据库字段类型。
55 | - ❎ 不支持 `分库分表`。
56 | - ❎ 不支持 `Docker` 部署,请自行研究。
57 | - ❎ 不支持 `分布式部署`。
58 | - ❎ 不支持 `Restful` 规范,不认同 `Restful` 规范,不使用 `Restful` 规范。
59 |
60 | ### 付费插件
61 |
62 | - `微信扫码插件`,登录注册,需要提供微信公众号。
63 | - `在线人数统计插件`,提供 `踢人`,`拉黑` 等功能。
64 | - `微信支付插件`,支持 `多产品`、`折扣`、`优惠` 等功能。
65 |
66 | ### 注意事项
67 |
68 | - 与本项目逻辑、BUG、建议相关的问题,请联系作者无偿 `免费处理`。
69 | - 与本项目无关的业务、功能、需求、部署相关的问题,请联系作者 `有偿咨询`。
70 |
71 | ### 实际效果
72 |
73 | 使用 `funpi` + `yiadmin` 驱动的,免费且开源的后台管理系统。
74 |
75 | #### 📄 登录页面
76 |
77 | 
78 |
79 | #### 📄 菜单页面
80 |
81 | 
82 |
83 | #### 📄 接口页面
84 |
85 | 
86 |
87 | #### 📄 角色页面
88 |
89 | 
90 |
91 | #### 📄 登录日志
92 |
93 | 
94 |
95 | #### 📄 邮件日志
96 |
97 | 
98 |
99 | ### 版权说明
100 |
101 | `funpi(放屁)` 使用 `Apache 2.0` 协议开源
102 |
103 | > 一句话总结:开源不等于放弃版权,不可侵犯原作者版权,改动处要做说明,可以闭源使用。
104 |
105 | 拥有版权(Copyright)意味着你对你开发的软件及其源代码拥有著作权,所有权和其他法定权利,使用一个开源协议并不意味着放弃版权。
106 |
107 | 在 `Apache 2.0` 协议许可下,您可以:
108 |
109 | - **商业化使用**(这意味着,您可以出于商业目的使用这些源代码)
110 | - **再分发**(这意味着,您可以将源代码副本传输给其他任何人)
111 | - **修改**(这意味着,您可以修改源代码)
112 | - **专利使用**(这意味着,版权人明确声明授予您专利使用权)
113 | - **私人使用**(这意味着,您可以出于一切目的私下使用和修改源代码)
114 |
115 | 唯须遵守以下条款:
116 |
117 | - **协议和版权通知**(这意味着,软件中必须包含许可证和版权声明的副本)
118 | - **状态更改说明**(如果您更改软件,您应当提供适当的说明)
119 |
120 | 除此之外,该软件:
121 |
122 | - **提供责任限制**(版权人声明不对使用者造成的任何损失负责)
123 | - **限制商标使用** (不能使用版权人的商标)
124 | - **不提供任何担保**(版权人声明不为该软件的品质提供任何担保)
125 |
126 | 进一步说明:
127 |
128 | 1. 本软件又叫本 **作品**,可以是源码,也可以是编译或转换后的其他形式。**衍生作品** 是在本作品的基础上修改后的有原创性的工作成果。本作品的 **贡献者** 包括许可人和其他提交了贡献的人,以下统称 **我**。
129 | 2. 我授予你权利:你可以免费复制、使用、修改、再许可、分发本作品及衍生作品(可以不用公开源码)。
130 | 3. 如果本软件涉及我的专利(或潜在专利),我在此授予你专利许可,你可以永久性地免费使用此专利,用于制作、使用、出售、转让本作品。如果你哪天居然告本作品侵权,你的专利许可在你告我那天被收回。
131 | 4. 你在复制和分发本作品或衍生作品时,要满足以下条件。
132 |
133 | - 带一份本许可证。
134 | - 如果你修改了什么,要在改动的文件中有明显的修改声明。
135 | - 如果你以源码形式分发,你必须保留本作品的版权、专利、商标和归属声明。
136 | - 如果本作品带了 **NOTICE** 文件,你就得带上 **NOTICE** 文件中包含的归属声明。即便你的发布是不带源码的,你也得带上此文件,并在作品某处予以展示。
137 | - 你可以对自己的修改添加版权说明。对于你的修改或者整个衍生作品,你可以使用不同的许可,但你对本作品的使用、复制和分发等,必须符合本许可证规定。
138 |
139 | 5. 你提交贡献就表明你默认遵守本许可的条款和条件。当然,你可以和我签订另外的专门的条款。
140 | 6. 你不许使用我的商品名、商标、服务标志或产品名。
141 | 7. 本作品是 **按原样**(AS IS)提供的,没有任何保证啊,你懂的。
142 | 8. 我可不负任何责任。除非我书面同意,或者法律有这样的要求(例如对故意和重大过失行为负责)。
143 | 9. 你可以向别人提供保证,你可以向别人收费,但那都是你的事,别给我惹麻烦。
144 |
145 | 注意以上的 **我**,既包含了许可人,也包含了每位 **贡献者**。
146 |
--------------------------------------------------------------------------------
/packages/funpi/utils/check.js:
--------------------------------------------------------------------------------
1 | // 内核模块
2 | import { resolve, basename } from 'pathe';
3 | import { existsSync, mkdirSync, readdirSync } from 'node:fs';
4 | // 外部模块
5 | import Ajv from 'ajv';
6 |
7 | // 内部模块
8 |
9 | // 配置文件
10 | import { appDir, funpiDir } from '../config/path.js';
11 |
12 | import { envConfig } from '../config/env.js';
13 | import { menuConfig as internalMenuConfig } from '../config/menu.js';
14 | // 协议配置
15 | import { envSchema } from '../schema/env.js';
16 | import { menuSchema } from '../schema/menu.js';
17 | // 工具函数
18 | import { fnImport, log4state, fnIsCamelCase, fnApiFiles, fnApiFilesCheck } from './index.js';
19 | import { ajvZh } from './ajvZh.js';
20 |
21 | export const initCheck = async (fastify) => {
22 | // 判断运行目录下是否有 funpi.js 文件
23 | if (existsSync(resolve(appDir, 'funpi.js')) === false) {
24 | console.log(`${log4state('warn')} 请在 funpi 项目根目录下运行`);
25 | process.exit();
26 | }
27 |
28 | // 确保关键目录存在 ==================================================
29 | if (existsSync(resolve(appDir, 'addons')) === false) {
30 | mkdirSync(resolve(appDir, 'addons'));
31 | }
32 | if (existsSync(resolve(appDir, 'apis')) === false) {
33 | mkdirSync(resolve(appDir, 'apis'));
34 | }
35 | if (existsSync(resolve(appDir, 'config')) === false) {
36 | mkdirSync(resolve(appDir, 'config'));
37 | }
38 | if (existsSync(resolve(appDir, 'tables')) === false) {
39 | mkdirSync(resolve(appDir, 'tables'));
40 | }
41 | if (existsSync(resolve(appDir, 'plugins')) === false) {
42 | mkdirSync(resolve(appDir, 'plugins'));
43 | }
44 | if (existsSync(resolve(appDir, 'logs')) === false) {
45 | mkdirSync(resolve(appDir, 'logs'));
46 | }
47 | if (existsSync(resolve(appDir, 'public')) === false) {
48 | mkdirSync(resolve(appDir, 'public'));
49 | }
50 |
51 | const ajv = new Ajv({
52 | strict: false,
53 | allErrors: true,
54 | verbose: false
55 | });
56 |
57 | // 验证配置文件 ==================================================
58 | const validEnvResult = ajv.validate(envSchema, envConfig);
59 | if (!validEnvResult) {
60 | ajvZh(ajv.errors);
61 | console.log(log4state('error'), '[ 环境变量错误 ] \n' + ajv.errorsText(ajv.errors, { separator: '\n' }));
62 | process.exit();
63 | }
64 |
65 | // 验证菜单配置
66 | const { menuConfig: userMenuConfig } = await fnImport(resolve(appDir, 'config', 'menu.js'), 'menuConfig', {});
67 | const allMenuConfig = [...userMenuConfig, ...internalMenuConfig];
68 | const validMenuResult = ajv.validate(menuSchema, allMenuConfig);
69 | if (!validMenuResult) {
70 | ajvZh(ajv.errors);
71 | console.log(log4state('error'), '[ 菜单配置错误 ] \n' + ajv.errorsText(ajv.errors, { separator: '\n' }));
72 | process.exit();
73 | }
74 |
75 | const menuPaths = new Set();
76 |
77 | for (const menu of allMenuConfig) {
78 | // 检查主菜单路径
79 | if (menuPaths.has(menu.path)) {
80 | console.log(log4state('error'), '[ 重复的菜单路径 ] ' + menu.path);
81 | process.exit();
82 | }
83 | menuPaths.add(menu.path);
84 |
85 | // 检查子菜单路径
86 | for (const child of menu.children) {
87 | if (menuPaths.has(child.path)) {
88 | console.log(log4state('error'), '[ 重复的菜单路径 ] ' + child.path);
89 | process.exit();
90 | }
91 | menuPaths.add(child.path);
92 | }
93 | }
94 |
95 | // 接口检测
96 | await fnApiFilesCheck();
97 |
98 | // ==================================================
99 |
100 | // 启动前验证
101 | if (process.env.MD5_SALT === 'funpi123456') {
102 | console.log(`${log4state('warn')} 请修改默认加密盐值!环境变量:MD5_SALT`);
103 | }
104 |
105 | if (process.env.DEV_PASSWORD === 'funpi123456') {
106 | console.log(`${log4state('warn')} 请修改开发管理员密码!环境变量:DEV_PASSWORD`);
107 | }
108 |
109 | if (process.env.JWT_SECRET === 'funpi123456') {
110 | console.log(`${log4state('warn')} 请修改 JWT 密钥!环境变量:JWT_SECRET`);
111 | }
112 | };
113 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/mailLog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 普通邮件
23 | 验证邮件
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
112 |
113 |
117 |
--------------------------------------------------------------------------------
/packages/funpi/apis/tool/sendMail.js:
--------------------------------------------------------------------------------
1 | import { randomInt } from 'es-toolkit';
2 | import { fnRoute, fnSchema } from '../../utils/index.js';
3 | import { httpConfig } from '../../config/http.js';
4 |
5 | export default async (fastify) => {
6 | fnRoute(import.meta.url, fastify, {
7 | // 请求参数约束
8 | schemaRequest: {
9 | oneOf: [
10 | {
11 | title: '发送验证码邮件',
12 | type: 'object',
13 | properties: {
14 | to_email: fnSchema({ name: '发送给谁', type: 'string', min: 5, max: 100 }),
15 | subject: fnSchema({ name: '邮件主题', type: 'string', min: 1, max: 300 }),
16 | verify_name: fnSchema({ name: '验证码名称', type: 'string', min: 2, max: 30, pattern: '^[a-z][a-zA-Z0-9]*' })
17 | },
18 | required: ['to_email', 'subject', 'verify_name']
19 | },
20 | {
21 | title: '发送普通邮件',
22 | type: 'object',
23 | properties: {
24 | to_email: fnSchema({ name: '发送给谁', type: 'string', min: 5, max: 100 }),
25 | subject: fnSchema({ name: '邮件主题', type: 'string', min: 1, max: 300 }),
26 | content: fnSchema({ name: '邮件内容', type: 'string', min: 1, max: 10000 })
27 | },
28 | required: ['to_email', 'subject', 'content']
29 | }
30 | ]
31 | },
32 | // 执行函数
33 | apiHandler: async (req) => {
34 | try {
35 | const mailLogModel = fastify.mysql.table('sys_mail_log');
36 | // 普通发送
37 | if (req.body.content) {
38 | await fastify.sendMail({
39 | to: req.body.to_email,
40 | subject: req.body.subject,
41 | text: req.body.content
42 | });
43 | await mailLogModel.clone().insertData({
44 | login_email: process.env.MAIL_USER,
45 | from_name: process.env.MAIL_FROM_NAME,
46 | from_email: process.env.MAIL_FROM_EMAIL,
47 | to_email: req.body.to_email,
48 | email_type: 'common',
49 | text_content: req.body.content
50 | });
51 | return {
52 | ...httpConfig.SUCCESS,
53 | msg: '邮件已发送',
54 | from: 'new'
55 | };
56 | }
57 |
58 | // 发送验证码
59 | if (req.body.verify_name) {
60 | // 如果已经发送过
61 | const existsVerifyCode = await fastify.redisGet(`${req.body.verify_name}:${req.body.to_email}`);
62 | if (existsVerifyCode) {
63 | return {
64 | ...httpConfig.SUCCESS,
65 | msg: '邮箱验证码已发送(5 分钟有效)',
66 | from: 'cache'
67 | };
68 | }
69 |
70 | // 如果没有发送过
71 | const cacheVerifyCode = randomInt(100000, 999999);
72 | await fastify.redisSet(`${req.body.verify_name}:${req.body.to_email}`, cacheVerifyCode, 60 * 5);
73 | await fastify.sendMail({
74 | to: req.body.to_email,
75 | subject: req.body.subject,
76 | text: req.body.subject + ':' + cacheVerifyCode
77 | });
78 | await mailLogModel.clone().insertData({
79 | login_email: process.env.MAIL_USER,
80 | from_name: process.env.MAIL_FROM_NAME,
81 | from_email: process.env.MAIL_FROM_EMAIL,
82 | to_email: req.body.to_email,
83 | email_type: 'verify',
84 | text_content: '******'
85 | });
86 | return {
87 | ...httpConfig.SUCCESS,
88 | msg: '邮箱验证码已发送(5 分钟有效)',
89 | from: 'new'
90 | };
91 | }
92 | } catch (err) {
93 | fastify.log.error(err);
94 | return httpConfig.FAIL;
95 | }
96 | }
97 | });
98 | };
99 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/admin/components/editDataDrawer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 添加管理员
5 | 编辑管理员
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
34 |
35 |
139 |
--------------------------------------------------------------------------------
/packages/funpi/bootstrap/auth.js:
--------------------------------------------------------------------------------
1 | // 内部模块
2 | import { existsSync } from 'node:fs';
3 | import { join } from 'pathe';
4 | // 外部模块
5 | import fp from 'fastify-plugin';
6 | import picomatch from 'picomatch';
7 | import { find as es_find } from 'es-toolkit/compat';
8 | // 配置文件
9 | import { appDir } from '../config/path.js';
10 | import { httpConfig } from '../config/http.js';
11 | // 工具函数
12 | import { fnApiCheck, fnDataClear } from '../utils/index.js';
13 |
14 | async function plugin(fastify) {
15 | fastify.addHook('onRequest', async (req) => {
16 | if (!req.headers['Authorization'] && !req.headers['authorization']) {
17 | req.headers['Authorization'] = 'Bearer funpi';
18 | }
19 | });
20 | fastify.addHook('preHandler', async (req, res) => {
21 | try {
22 | // 如果是收藏图标,则直接通过
23 | if (req.url === 'favicon.ico') return;
24 | if (req.url === '/') {
25 | res.send({
26 | code: 0,
27 | msg: `${process.env.APP_NAME} 接口程序已启动`
28 | });
29 | return;
30 | }
31 | if (req.url.startsWith('/swagger/')) return;
32 |
33 | /* --------------------------------- 请求资源判断 --------------------------------- */
34 | if (req.url.startsWith('/public')) {
35 | const filePath = join(appDir, req.url);
36 | if (existsSync(filePath) === true) {
37 | return;
38 | } else {
39 | // 文件不存在
40 | res.send(httpConfig.NO_FILE);
41 | return;
42 | }
43 | }
44 |
45 | if (!req.routeOptions?.url || !req.routeOptions?.url?.startsWith('/api/')) {
46 | res.send(httpConfig.NO_API);
47 | return;
48 | }
49 |
50 | if (req.routeOptions.url === '/api/funpi/admin/adminLogin') return;
51 | if (req.routeOptions.url === '/api/funpi/tool/tokenCheck') return;
52 |
53 | /* --------------------------------- 解析用户登录参数 --------------------------------- */
54 | let isAuthFail = false;
55 | try {
56 | await req.jwtVerify();
57 | } catch (err) {
58 | req.session = {
59 | id: 0,
60 | username: 'visitor',
61 | nickname: '游客',
62 | role_type: 'user',
63 | role: 'visitor'
64 | };
65 | isAuthFail = true;
66 | }
67 |
68 | /* ---------------------------------- 日志记录 ---------------------------------- */
69 |
70 | fastify.log.warn({
71 | apiPath: req?.url,
72 | body: fnDataClear(req.body),
73 | session: req?.session,
74 | reqId: req?.id
75 | });
76 |
77 | /* --------------------------------- 接口存在性判断 -------------------------------- */
78 | const allApiNames = await fastify.redisGet('cacheData:apiNames');
79 |
80 | if (allApiNames.includes(req.routeOptions.url) === false) {
81 | res.send(httpConfig.NO_API);
82 | return;
83 | }
84 |
85 | /* --------------------------------- 上传参数检测 --------------------------------- */
86 | if (process.env.PARAMS_CHECK === '1') {
87 | const result = await fnApiCheck(req);
88 | if (result.code !== 0) {
89 | res.send({
90 | ...httpConfig.PARAMS_SIGN_FAIL,
91 | detail: result?.msg || ''
92 | });
93 | return;
94 | }
95 | }
96 |
97 | /* ---------------------------------- 角色接口权限判断 --------------------------------- */
98 | // 如果接口不在白名单中,则判断用户是否有接口访问权限
99 | const userApis = await fastify.getUserApis(req.session);
100 |
101 | const hasApi = es_find(userApis, ['value', req.routeOptions.url]);
102 | /* --------------------------------- 接口登录检测 --------------------------------- */
103 |
104 | if (!hasApi) {
105 | res.send({
106 | ...httpConfig.FAIL,
107 | msg: `您没有 [ ${req?.routeOptions?.schema?.summary || req.routeOptions.url} ] 接口的操作权限`,
108 | login: isAuthFail === false ? 'yes' : 'no'
109 | });
110 | return;
111 | }
112 | } catch (err) {
113 | fastify.log.error(err);
114 | res.send({
115 | ...httpConfig.FAIL,
116 | msg: err.msg || '认证异常',
117 | other: err.other || ''
118 | });
119 | return res;
120 | }
121 | });
122 | }
123 | export default fp(plugin, { name: 'funpiAuth', dependencies: ['funpiCors', 'funpiMysql', 'funpiTool'] });
124 |
--------------------------------------------------------------------------------
/packages/funpi/bootstrap/tool.js:
--------------------------------------------------------------------------------
1 | // 外部模块
2 | import fp from 'fastify-plugin';
3 | // 配置文件
4 |
5 | async function plugin(fastify) {
6 | // 设置 redis
7 | const redisSet = async (key, value, second = 0) => {
8 | try {
9 | if (second > 0) {
10 | await fastify.redis.set(`${process.env.REDIS_KEY_PREFIX}:${key}`, JSON.stringify(value), 'EX', second);
11 | } else {
12 | await fastify.redis.set(`${process.env.REDIS_KEY_PREFIX}:${key}`, JSON.stringify(value));
13 | }
14 | } catch (err) {
15 | fastify.log.warn(err);
16 | }
17 | };
18 |
19 | // 获取 redis
20 | const redisGet = async (key) => {
21 | try {
22 | const result = await fastify.redis.get(`${process.env.REDIS_KEY_PREFIX}:${key}`);
23 | return JSON.parse(result);
24 | } catch (err) {
25 | fastify.log.warn(err);
26 | }
27 | };
28 | // 删除 redis
29 | const redisDel = async (key) => {
30 | try {
31 | await fastify.redis.del(`${process.env.REDIS_KEY_PREFIX}:${key}`);
32 | } catch (err) {
33 | fastify.log.warn(err);
34 | }
35 | };
36 |
37 | const getUserApis = async (session) => {
38 | if (!session) return [];
39 | // 提取当前用户的角色码组
40 |
41 | // 提取所有角色拥有的接口
42 | let apiIds = [];
43 | const dataRoleCodes = await redisGet('cacheData:role');
44 | dataRoleCodes.forEach((item) => {
45 | if (session.role === item.code) {
46 | apiIds = item.api_ids
47 | .split(',')
48 | .filter((id) => id !== '')
49 | .map((id) => Number(id));
50 | }
51 | });
52 |
53 | // 将接口进行唯一性处理
54 | const userApiIds = [...new Set(apiIds)];
55 | const dataApi = await redisGet('cacheData:api');
56 | // 最终的用户接口列表
57 | const result = dataApi.filter((item) => {
58 | return userApiIds.includes(item.id);
59 | });
60 | return result;
61 | };
62 |
63 | const getUserMenus = async (session) => {
64 | try {
65 | if (!session) return [];
66 |
67 | // 所有菜单 ID
68 | let menuIds = [];
69 |
70 | const dataRoleCodes = await redisGet('cacheData:role');
71 | dataRoleCodes.forEach((item) => {
72 | if (session.role === item.code) {
73 | menuIds = item.menu_ids
74 | .split(',')
75 | .filter((id) => id !== '')
76 | .map((id) => Number(id));
77 | }
78 | });
79 |
80 | const userMenuIds = [...new Set(menuIds)];
81 | const dataMenu = await redisGet('cacheData:menu');
82 |
83 | const result = dataMenu.filter((item) => {
84 | return userMenuIds.includes(item.id);
85 | });
86 | return result;
87 | } catch (err) {
88 | fastify.log.error(err);
89 | }
90 | };
91 |
92 | const cacheMenuData = async () => {
93 | // 菜单列表
94 | const dataMenu = await fastify.mysql.table('sys_menu').selectAll();
95 |
96 | // 菜单树数据
97 | await redisSet('cacheData:menu', []);
98 | await redisSet('cacheData:menu', dataMenu);
99 | };
100 |
101 | const cacheApiData = async () => {
102 | // 菜单列表
103 | const dataApi = await fastify.mysql.table('sys_api').selectAll();
104 |
105 | // 白名单接口
106 | // const apiWhiteLists = dataApi.filter((item) => item.state === 1).map((item) => `${item.value}`);
107 | // 黑名单接口
108 | // const apiBlackLists = dataApi.filter((item) => item.state === 2).map((item) => `${item.value}`);
109 |
110 | // 接口树数据
111 | await redisSet('cacheData:api', []);
112 | await redisSet('cacheData:api', dataApi);
113 |
114 | // 接口名称缓存
115 | await redisSet('cacheData:apiNames', []);
116 | await redisSet(
117 | 'cacheData:apiNames',
118 | dataApi.filter((item) => item.pid !== 0).map((item) => `${item.value}`)
119 | );
120 | };
121 |
122 | const cacheRoleData = async () => {
123 | // 角色类别
124 | const dataRole = await fastify.mysql.table('sys_role').selectAll();
125 |
126 | await redisSet('cacheData:role', []);
127 | await redisSet('cacheData:role', dataRole);
128 | };
129 |
130 | // 设置和获取缓存数据
131 | fastify.decorate('redisSet', redisSet);
132 | fastify.decorate('redisGet', redisGet);
133 | fastify.decorate('redisDel', redisDel);
134 | // 获取当前登录用户可操作的接口列表
135 | fastify.decorate('getUserApis', getUserApis);
136 | // 获取用户的菜单
137 | fastify.decorate('getUserMenus', getUserMenus);
138 | // 设置权限数据
139 | fastify.decorate('cacheMenuData', cacheMenuData);
140 | // 设置权限数据
141 | fastify.decorate('cacheApiData', cacheApiData);
142 | // 设置角色数据
143 | fastify.decorate('cacheRoleData', cacheRoleData);
144 | }
145 | export default fp(plugin, { name: 'funpiTool', dependencies: ['funpiRedis', 'funpiMysql'] });
146 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/appConfig/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
120 |
121 |
125 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/dict/components/editDataDrawer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 添加字典
5 | 编辑字典
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 字符串
26 | 数字
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
41 |
42 |
43 |
44 |
148 |
--------------------------------------------------------------------------------
/packages/admin/src/pages/internal/dictCategory/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 操作
23 |
24 | 编辑
25 | 删除
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
148 |
149 |
153 |
--------------------------------------------------------------------------------