init base app modules

This commit is contained in:
熊潇 2025-07-01 18:53:28 +08:00
commit d96c342d3e
23 changed files with 3311 additions and 0 deletions

68
.gitignore vendored Normal file
View File

@ -0,0 +1,68 @@
node_modules
# mac
.DS_Store
.env*
!.env*example
dist
build
logs
.turbo
pack-dist
# astro
.astro
# next
.next
# nuxt
.nuxt
# vercel
.vercel
# vuepress
.vuepress/dist
# coverage
coverage/
# typescript
*.tsbuildinfo
# debug logs
*.log
*.tmp
# vscode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# idea
.idea
# system
Thumbs.db
ehthumbs.db
Desktop.ini
# temp files
*.tmp
*.temp
# local development
*.local
public/r
.pnpm-store
storage/

3
.npmrc Normal file
View File

@ -0,0 +1,3 @@
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
ignore-workspace-root-check=true

View File

@ -0,0 +1,25 @@
// @ts-check
import { resolvePath } from '@kevisual/use-config/env';
import { execSync } from 'node:child_process';
const entry = 'src/index.ts';
const naming = 'app';
const external = ['sequelize', 'pg', 'sqlite3', 'minio', '@kevisual/router', 'pm2'];
/**
* @type {import('bun').BuildConfig}
*/
await Bun.build({
target: 'node',
format: 'esm',
entrypoints: [resolvePath(entry, { meta: import.meta })],
outdir: resolvePath('./dist', { meta: import.meta }),
naming: {
entry: `${naming}.js`,
},
external: external,
env: 'KEVISUAL_*',
});
// const cmd = `dts -i src/index.ts -o app.d.ts`;
// const cmd = `dts -i ${entry} -o ${naming}.d.ts`;
// execSync(cmd, { stdio: 'inherit' });

View File

@ -0,0 +1,27 @@
{
"name": "aliyun-ai",
"version": "0.0.1",
"description": "",
"main": "index.js",
"basename": "/root/aliyun-ai",
"app": {
"key": "aliyun-ai",
"entry": "dist/app.js",
"type": "system-app"
},
"files": [
"dist"
],
"scripts": {
"dev": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 bun --watch src/dev.ts ",
"build": "rimraf dist && bun run bun.config.mjs",
"clean": "rm -rf dist",
"pub": "npm run build && envision pack -p -u"
},
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT",
"packageManager": "pnpm@10.12.1",
"type": "module",
"devDependencies": {}
}

View File

@ -0,0 +1,4 @@
import { app } from '@/modules/router.ts';
import { oss } from '@/modules/minio.ts';
import { config } from '@/modules/config.ts';
export { app, config, oss };

View File

@ -0,0 +1,6 @@
import { app } from './index.ts';
app.listen(4000, () => {
console.log('Server is running on http://localhost:4000');
console.log('Press Ctrl+C to stop the server');
});

View File

@ -0,0 +1,5 @@
import { app } from './app.ts';
import './routes/create-audio.ts';
export { app };

View File

@ -0,0 +1,75 @@
import { app, config, oss } from '../app.ts';
import { dashscopeTTS } from '@/examples/dash-scope/tts.ts';
import { randomLetter } from '@/utils/random.ts';
import dayjs from 'dayjs';
type DashScopeTTSResponse = {
output?: {
finish_reason?: string;
audio?: {
expires_at?: number;
data?: string;
id?: string;
url?: string;
};
};
usage?: {
input_tokens_details?: {
text_tokens?: number;
};
total_tokens?: number;
output_tokens?: number;
input_tokens?: number;
output_tokens_details?: {
audio_tokens?: number;
text_tokens?: number;
};
};
request_id?: string;
};
app
.route({
path: 'aliyun-ai',
key: 'createVideos',
})
.define(async (ctx) => {
const { text, model, save = 'none' } = ctx.query;
if (!text) {
ctx.throw(400, 'Text and model are required parameters');
}
const value: DashScopeTTSResponse = await dashscopeTTS({
text,
voice: model || 'Chelsie',
token: config.BAILIAN_API_KEY,
});
const url = value?.output?.audio?.url;
const fileName = `audio-${randomLetter(32)}.wav`;
const username = 'share';
const today = dayjs().format('YYYY-MM-DD');
// 使用用户名和日期作为文件夹路径
const filePath = `${username}/storage/aliyun-ai/audio/${today}/${fileName}`;
if (url) {
ctx.body = {
audioUrl: url,
};
}
if (save === 'minio' && url) {
// 读取文件url地址的数据并保存到 MinIO, 文件是 音频 wave 格式
const audioData = await fetch(url).then((res) => res.arrayBuffer());
// 将音频数据转换为 Buffer 并上传到 MinIO
const buffer = Buffer.from(audioData);
await oss.client.putObject(oss.bucketName, filePath, buffer, null, {
'Content-Type': 'audio/wav',
'app-source': 'aliyun-ai',
share: 'public',
});
console.log('Audio file uploaded to MinIO:', filePath);
// @ts-ignore
ctx.body.minio = filePath;
}
// 如果没有 url抛出错误
if (!url) {
ctx.throw(500, 'Failed to create audio');
}
})
.addTo(app);

25
bun.config.mjs Normal file
View File

@ -0,0 +1,25 @@
// @ts-check
import { resolvePath } from '@kevisual/use-config/env';
import { execSync } from 'node:child_process';
const entry = 'src/index.ts';
const naming = 'app';
const external = ['sequelize', 'pg', 'sqlite3', 'minio', '@kevisual/router', 'pm2'];
/**
* @type {import('bun').BuildConfig}
*/
await Bun.build({
target: 'node',
format: 'esm',
entrypoints: [resolvePath(entry, { meta: import.meta })],
outdir: resolvePath('./dist', { meta: import.meta }),
naming: {
entry: `${naming}.js`,
},
external: external,
env: 'KEVISUAL_*',
});
// const cmd = `dts -i src/index.ts -o app.d.ts`;
const cmd = `dts -i ${entry} -o ${naming}.d.ts`;
execSync(cmd, { stdio: 'inherit' });

66
package.json Normal file
View File

@ -0,0 +1,66 @@
{
"name": "ai-pages-services",
"version": "0.0.1",
"description": "",
"main": "index.js",
"basename": "/root/ai-pages-services",
"app": {
"key": "ai-pages-services",
"entry": "dist/app.js",
"type": "system-app"
},
"files": [
"dist"
],
"scripts": {
"dev": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 bun --watch src/dev.ts ",
"build": "rimraf dist && bun run bun.config.mjs",
"test": "tsx test/**/*.ts",
"clean": "rm -rf dist",
"pub": "npm run build && envision pack -p -u",
"cmd": "tsx cmd/index.ts "
},
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me>",
"license": "MIT",
"type": "module",
"types": "types/index.d.ts",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@kevisual/code-center-module": "0.0.23",
"@kevisual/router": "0.0.22",
"@kevisual/use-config": "^1.0.18",
"cookie": "^1.0.2",
"dayjs": "^1.11.13",
"formidable": "^3.5.4",
"lodash-es": "^4.17.21"
},
"devDependencies": {
"@kevisual/context": "^0.0.3",
"@kevisual/logger": "^0.0.4",
"@kevisual/oss": "^0.0.12",
"@kevisual/types": "^0.0.10",
"@kevisual/use-config": "^1.0.19",
"@types/bun": "^1.2.16",
"@types/crypto-js": "^4.2.2",
"@types/formidable": "^3.4.5",
"@types/lodash-es": "^4.17.12",
"@types/node": "^24.0.3",
"commander": "^14.0.0",
"concurrently": "^9.1.2",
"cross-env": "^7.0.3",
"inquire": "^0.4.8",
"ioredis": "^5.6.1",
"minio": "^8.0.5",
"nanoid": "^5.1.5",
"nodemon": "^3.1.10",
"pg": "^8.16.1",
"rimraf": "^6.0.1",
"sequelize": "^6.37.7",
"tape": "^5.9.0",
"typescript": "^5.8.3"
},
"packageManager": "pnpm@10.12.1"
}

2789
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
packages:
- 'apps/*'

View File

@ -0,0 +1,30 @@
// 使用DashScope API进行TTS (文本转语音) 请求
export const dashscopeTTS = async ({ text, voice = 'Chelsie', token }) => {
try {
const response = await fetch('https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
// model: 'qwen-tts',
model: 'qwen-tts-latest',
input: {
text,
voice,
},
}),
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('TTS 请求失败:', error);
throw error;
}
};

4
src/modules/config.ts Normal file
View File

@ -0,0 +1,4 @@
import { useConfig } from '@kevisual/use-config';
export const config = useConfig();
export const isDev = config.ENV === 'development';

6
src/modules/logger.ts Normal file
View File

@ -0,0 +1,6 @@
import { Logger } from '@kevisual/logger';
import { config } from './config.ts';
export const logger = new Logger({
level: config.LOG_LEVEL || 'info',
showTime: true,
});

41
src/modules/minio.ts Normal file
View File

@ -0,0 +1,41 @@
import { Client, ClientOptions } from 'minio';
import { config } from './config.ts';
import { OssBase } from '@kevisual/oss/services';
import { useContextKey } from '@kevisual/context';
const minioConfig = {
endPoint: config.MINIO_ENDPOINT || 'localhost',
port: parseInt(config.MINIO_PORT || '9000'),
useSSL: config.MINIO_USE_SSL === 'true',
accessKey: config.MINIO_ACCESS_KEY,
secretKey: config.MINIO_SECRET_KEY,
};
export const minioClient = useContextKey('minioClient', () => {
return new Client(minioConfig);
});
export const bucketName = config.MINIO_BUCKET_NAME || 'resources';
if (!minioClient) {
throw new Error('Minio client not initialized');
}
export const check = () => {
// 验证权限
(async () => {
const bucketExists = await minioClient.bucketExists(bucketName);
if (!bucketExists) {
await minioClient.makeBucket(bucketName);
}
console.log('bucketExists', bucketExists);
// const res = await minioClient.putObject(bucketName, 'root/test/0.0.1/a.txt', 'test');
// console.log('minio putObject', res);
})();
};
export const oss = useContextKey(
'oss',
new OssBase({
client: minioClient,
bucketName: bucketName,
prefix: '',
}),
);

3
src/modules/notify.ts Normal file
View File

@ -0,0 +1,3 @@
export const notify = () => {
//
};

38
src/modules/redis.ts Normal file
View File

@ -0,0 +1,38 @@
import { Redis } from 'ioredis';
import { config } from './config.ts';
import { useContextKey } from '@kevisual/context';
const redisConfig = {
host: config.REDIS_HOST || 'localhost',
port: parseInt(config.REDIS_PORT || '6379'),
password: config.REDIS_PASSWORD,
};
export const createRedisClient = (options = {}) => {
const redisClient = new Redis({
host: 'localhost', // Redis 服务器的主机名或 IP 地址
port: 6379, // Redis 服务器的端口号
// password: 'your_password', // Redis 的密码 (如果有)
db: 0, // 要使用的 Redis 数据库索引 (0-15)
keyPrefix: '', // key 前缀
retryStrategy(times) {
// 连接重试策略
return Math.min(times * 50, 2000); // 每次重试时延迟增加
},
maxRetriesPerRequest: null, // 允许请求重试的次数 (如果需要无限次重试)
...redisConfig,
...options,
});
redisClient.on('connect', () => {
console.log('Redis client connected successfully');
});
redisClient.on('error', (err) => {
console.error('Redis client error:', err);
});
return redisClient;
};
// 配置 Redis 连接
export const redis = useContextKey('redis', () => createRedisClient());
// 初始化 Redis 客户端
// export const redisPublisher = createRedisClient(); // 用于发布消息
// export const redisSubscriber = createRedisClient(); // 用于订阅消息

6
src/modules/router.ts Normal file
View File

@ -0,0 +1,6 @@
import { App } from '@kevisual/router';
import { useContextKey } from '@kevisual/context';
const init = () => {
return new App();
};
export const app = useContextKey('app', init);

34
src/modules/sequelize.ts Normal file
View File

@ -0,0 +1,34 @@
import { Sequelize } from 'sequelize';
import { config } from './config.ts';
import { useContextKey } from '@kevisual/context';
export type PostgresConfig = {
postgres: {
username: string;
password: string;
host: string;
port: number;
database: string;
};
};
if (!config.POSTGRES_PASSWORD || !config.POSTGRES_USER) {
console.error('postgres config is required password and user');
process.exit(1);
}
const postgresConfig = {
username: config.POSTGRES_USER,
password: config.POSTGRES_PASSWORD,
host: config.POSTGRES_HOST || 'localhost',
port: parseInt(config.POSTGRES_PORT || '5432'),
database: config.POSTGRES_DB || 'postgres',
};
export const init = async () => {
return new Sequelize({
dialect: 'postgres',
...postgresConfig,
// logging: false,
});
};
export const sequelize = useContextKey('sequelize', () => init());

9
src/modules/user.ts Normal file
View File

@ -0,0 +1,9 @@
import { sequelize, User, UserInit, Org, OrgInit } from '@kevisual/code-center-module';
export { sequelize, User, UserInit, Org, OrgInit };
export const init = () => {
UserInit();
OrgInit();
};
init();

26
src/utils/random.ts Normal file
View File

@ -0,0 +1,26 @@
import { customAlphabet } from 'nanoid';
export const letter = 'abcdefghijklmnopqrstuvwxyz';
export const number = '0123456789';
const alphanumeric = `${letter}${number}`;
export const alphanumericWithDash = `${alphanumeric}-`;
export const uuid = customAlphabet(letter);
export const nanoid = customAlphabet(alphanumeric, 10);
export const nanoidWithDash = customAlphabet(alphanumericWithDash, 10);
/**
* id
* @param number
* @returns
*/
export const randomId = (number: number) => {
const _letter = uuid(1);
return `${_letter}${nanoid(number)}`;
};
export const randomLetter = (number: number = 8, opts?: { before?: string; after?: string }) => {
const { before = '', after = '' } = opts || {};
return `${before}${uuid(number)}${after}`;
};

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "@kevisual/types/json/backend.json",
"compilerOptions": {
"baseUrl": ".",
"typeRoots": [
"./node_modules/@types",
"./node_modules/@kevisual"
],
"paths": {
"@/*": [
"src/*"
]
},
},
"include": [
"src/**/*",
"apps/**/*",
],
}