generated from template/router-template
update
This commit is contained in:
21
backend/bun.config.mjs
Normal file
21
backend/bun.config.mjs
Normal file
@@ -0,0 +1,21 @@
|
||||
// @ts-check
|
||||
import { resolvePath } from '@kevisual/use-config';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
const entry = 'src/index.ts';
|
||||
const naming = 'app';
|
||||
const external = ['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,
|
||||
env: 'KEVISUAL_*',
|
||||
});
|
||||
@@ -3,12 +3,27 @@
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"basename": "/root/daily-question-server",
|
||||
"app": {
|
||||
"type": "system-app",
|
||||
"key": "daily-question-server",
|
||||
"entry": "app.js",
|
||||
"runtime": [
|
||||
"server"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "bun --watch src/main.ts ",
|
||||
"build": "rimraf dist && bun run bun.config.mjs",
|
||||
"compile": "bun build --compile ./src/main.ts --outfile myapp",
|
||||
"compile:win": "bun build --compile ./src/main.ts --target=bun-windows-x64 --outfile myapp.exe",
|
||||
"build": "rimraf dist && rimraf pack-dist && bun run bun.config.mjs",
|
||||
"postbuild": "ev pack",
|
||||
"compile": "bun build --compile ./src/main.ts --outfile daily-question",
|
||||
"compile:win": "bun build --compile ./src/main.ts --target=bun-windows-x64 --outfile daily-question.exe",
|
||||
"postcompile:win": "bun zip.ts",
|
||||
"clean": "rm -rf dist",
|
||||
"sc": "rimraf *.zip && rimraf *.exe",
|
||||
"pub": "envision pack -p -u"
|
||||
},
|
||||
"keywords": [],
|
||||
@@ -18,7 +33,7 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@kevisual/ai": "^0.0.12",
|
||||
"@kevisual/local-proxy": "^0.0.6",
|
||||
"@kevisual/local-proxy": "^0.0.8",
|
||||
"@kevisual/query": "^0.0.29",
|
||||
"@kevisual/router": "0.0.33",
|
||||
"@kevisual/use-config": "^1.0.21",
|
||||
@@ -34,6 +49,7 @@
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/bun": "^1.3.3",
|
||||
"@types/node": "^24.10.1",
|
||||
"archiver": "^7.0.1",
|
||||
"drizzle-kit": "^0.31.7"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
## 后端功能
|
||||
## 使用方法
|
||||
|
||||
运行exe后,浏览器访问 http://localhost:9000/
|
||||
|
||||
## 后端功能提示词
|
||||
|
||||
使用db,better-sqite3
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { App } from "@kevisual/router";
|
||||
import { useContextKey } from "@kevisual/use-config/context";
|
||||
export const app = useContextKey('app', new App({
|
||||
export const app: App = useContextKey<App>('app', new App({
|
||||
serverOptions: {
|
||||
path: '/client/router'
|
||||
}
|
||||
|
||||
4
backend/src/index.ts
Normal file
4
backend/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { app } from './app.ts'
|
||||
import './router/index.ts';
|
||||
|
||||
export { app }
|
||||
@@ -1,16 +1,15 @@
|
||||
import { app } from './app.ts'
|
||||
import './router/daily/index.ts';
|
||||
import './router/library/index.ts';
|
||||
import './router/index.ts';
|
||||
|
||||
import { proxyRoute, initProxy } from '@kevisual/local-proxy/proxy.ts';
|
||||
|
||||
initProxy({
|
||||
pagesDir: './public',
|
||||
watch: true,
|
||||
home: '/root/daily-question'
|
||||
});
|
||||
export { app }
|
||||
if (require.main === module) {
|
||||
initProxy({
|
||||
pagesDir: './public',
|
||||
watch: true,
|
||||
home: '/root/daily-question'
|
||||
});
|
||||
app.listen(9000, () => {
|
||||
console.log('Server is running on http://localhost:9000');
|
||||
});
|
||||
|
||||
@@ -23,7 +23,6 @@ export const getDb = () => {
|
||||
export const init = async () => {
|
||||
console.log('Database path:', dbPath);
|
||||
const database = getDb();
|
||||
// 使用 Drizzle ORM 创建表 (如果不存在)
|
||||
if (sqliteDb) {
|
||||
sqliteDb.run(`
|
||||
CREATE TABLE IF NOT EXISTS question_library (
|
||||
@@ -34,6 +33,8 @@ export const init = async () => {
|
||||
repeat INTEGER DEFAULT 0,
|
||||
isUse INTEGER DEFAULT 0,
|
||||
usedAt TEXT,
|
||||
uid TEXT,
|
||||
data TEXT,
|
||||
createdAt TEXT NOT NULL,
|
||||
updatedAt TEXT NOT NULL
|
||||
)
|
||||
@@ -49,6 +50,8 @@ export const init = async () => {
|
||||
createdAt TEXT NOT NULL,
|
||||
updatedAt TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
uid TEXT,
|
||||
data TEXT,
|
||||
qid TEXT,
|
||||
FOREIGN KEY (qid) REFERENCES question_library(id)
|
||||
)
|
||||
|
||||
@@ -12,8 +12,10 @@ export const questionLibrary = sqliteTable(
|
||||
repeat: integer({ mode: 'boolean' }).default(false),
|
||||
isUse: integer({ mode: 'boolean' }).default(false),
|
||||
usedAt: text(), // YYYY-MM-DD format
|
||||
uid: text().notNull().default(''), // 用户ID,默认系统用户
|
||||
createdAt: text().notNull().default(new Date().toISOString()),
|
||||
updatedAt: text().notNull().default(new Date().toISOString()).$onUpdate(() => new Date().toISOString()),
|
||||
data: text(), // JSON string
|
||||
},
|
||||
(table) => ({
|
||||
isUseIdx: index('idx_library_isUse').on(table.isUse),
|
||||
@@ -33,7 +35,9 @@ export const dailyQuestions = sqliteTable(
|
||||
createdAt: text().notNull().default(new Date().toISOString()),
|
||||
updatedAt: text().notNull().default(new Date().toISOString()).$onUpdate(() => new Date().toISOString()),
|
||||
date: text().notNull(), // YYYY-MM-DD format
|
||||
uid: text().notNull().default(''), // 用户ID,默认系统用户
|
||||
qid: text().references(() => questionLibrary.id),
|
||||
data: text(), // JSON string
|
||||
},
|
||||
(table) => ({
|
||||
dateIdx: index('idx_daily_date').on(table.date),
|
||||
|
||||
@@ -2,12 +2,13 @@ import { generateId, getTodayDate } from '../module/utils.ts';
|
||||
import { app } from '../app.ts';
|
||||
import { getDb } from '../module/db.ts';
|
||||
import { dailyQuestions, questionLibrary } from '../module/schema.ts';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { desc, eq } from 'drizzle-orm';
|
||||
|
||||
app.route({
|
||||
path: 'daily',
|
||||
key: 'random',
|
||||
description: '随机获取一条未使用的问题'
|
||||
description: '随机获取一条未使用的问题',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const force = ctx.query.force ?? false;
|
||||
const db = getDb();
|
||||
@@ -37,14 +38,18 @@ app.route({
|
||||
.from(questionLibrary)
|
||||
.where(eq(questionLibrary.isUse, false));
|
||||
|
||||
let selectedQuestion;
|
||||
if (unusedQuestions.length === 0) {
|
||||
ctx.throw(404, '没有未使用的问题');
|
||||
return;
|
||||
selectedQuestion = {
|
||||
title: '今天发生了什么有趣的事情?',
|
||||
description: '请描述一下你今天经历的有趣事件,分享你的感受和想法。',
|
||||
tags: JSON.stringify(['生活', '分享']),
|
||||
}
|
||||
} else {
|
||||
const randomIndex = Math.floor(Math.random() * unusedQuestions.length);
|
||||
selectedQuestion = unusedQuestions[randomIndex];
|
||||
}
|
||||
|
||||
const randomIndex = Math.floor(Math.random() * unusedQuestions.length);
|
||||
const selectedQuestion = unusedQuestions[randomIndex];
|
||||
|
||||
// 更新questionLibrary中的isUse和usedAt字段
|
||||
await db
|
||||
.update(questionLibrary)
|
||||
@@ -60,8 +65,9 @@ app.route({
|
||||
.insert(dailyQuestions)
|
||||
.values({
|
||||
id: generateId(),
|
||||
qid: selectedQuestion.id,
|
||||
qid: selectedQuestion.id || '',
|
||||
title: selectedQuestion.title,
|
||||
summary: selectedQuestion?.description,
|
||||
description: '',
|
||||
tags: selectedQuestion.tags,
|
||||
date: day,
|
||||
@@ -78,11 +84,12 @@ app.route({
|
||||
app.route({
|
||||
description: '获取今天的问题',
|
||||
path: 'daily',
|
||||
key: 'today'
|
||||
key: 'today',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const db = getDb();
|
||||
const day = getTodayDate();
|
||||
|
||||
const token = ctx.query?.token;
|
||||
try {
|
||||
const todayQuestion = await db
|
||||
.select()
|
||||
@@ -95,11 +102,15 @@ app.route({
|
||||
const res = await ctx.call({
|
||||
path: 'daily',
|
||||
key: 'random',
|
||||
payload: {
|
||||
token: token,
|
||||
}
|
||||
});
|
||||
if (res.code === 200) {
|
||||
ctx.body = res.body;
|
||||
return;
|
||||
}
|
||||
console.error(res.message);
|
||||
ctx.throw(500, '获取今天的问题失败');
|
||||
}
|
||||
|
||||
|
||||
@@ -2,22 +2,41 @@ import { generateId } from '../../module/utils.ts';
|
||||
import { app } from '../../app.ts';
|
||||
import { getDb } from '../../module/db.ts';
|
||||
import { dailyQuestions } from '../../module/schema.ts';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { eq, and, or, like } from 'drizzle-orm';
|
||||
|
||||
// 列出每日问题
|
||||
app.route({
|
||||
description: '列出每日问题',
|
||||
path: 'daily',
|
||||
key: 'list'
|
||||
key: 'list',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const query = ctx.query;
|
||||
const page = query.page ?? 1;
|
||||
const pageSize = query.pageSize ?? 99999;
|
||||
const db = getDb();
|
||||
const search = query.search ?? '';
|
||||
const id = query.id ?? '';
|
||||
try {
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
const allResults = await db.select().from(dailyQuestions);
|
||||
const allResults = await db.select().from(dailyQuestions).where(() => {
|
||||
const conditions = [];
|
||||
|
||||
if (search) {
|
||||
conditions.push(like(dailyQuestions.title, `%${search}%`));
|
||||
}
|
||||
|
||||
if (id) {
|
||||
conditions.push(eq(dailyQuestions.id, id));
|
||||
}
|
||||
|
||||
// 如果需要 OR 逻辑(search 或 id 任一匹配)
|
||||
return conditions.length > 0 ? or(...conditions) : undefined;
|
||||
|
||||
// 如果需要 AND 逻辑(search 和 id 都要匹配)
|
||||
// return conditions.length > 0 ? and(...conditions) : undefined;
|
||||
});
|
||||
|
||||
// Sort by createdAt in descending order (newest first)
|
||||
const sortedResults = allResults.sort((a, b) =>
|
||||
@@ -47,7 +66,8 @@ app.route({
|
||||
app.route({
|
||||
description: '更新每日问题',
|
||||
path: 'daily',
|
||||
key: 'update'
|
||||
key: 'update',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const query = ctx.query;
|
||||
const id = query.id;
|
||||
@@ -102,7 +122,8 @@ app.route({
|
||||
app.route({
|
||||
description: '删除每日问题',
|
||||
path: 'daily',
|
||||
key: 'delete'
|
||||
key: 'delete',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const query = ctx.query;
|
||||
const id = query.id;
|
||||
@@ -128,7 +149,8 @@ app.route({
|
||||
app.route({
|
||||
description: '获取每日问题详情',
|
||||
path: 'daily',
|
||||
key: 'detail'
|
||||
key: 'detail',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const query = ctx.query;
|
||||
const id = query.id;
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
import './daily/index.ts'
|
||||
import './library/index.ts'
|
||||
import './daily-task.ts'
|
||||
import './library-task.ts'
|
||||
import './library-task.ts'
|
||||
import { app } from '../app.ts';
|
||||
const hasAuth = app.router.routes.some(r => r.id === 'auth');
|
||||
if (!hasAuth) {
|
||||
console.log('添加认证中间件路由');
|
||||
app.route({
|
||||
path: 'auth',
|
||||
key: 'auth',
|
||||
description: '用户认证',
|
||||
id: 'auth'
|
||||
}).define(async (ctx) => {
|
||||
// 这里可以添加实际的认证逻辑
|
||||
}).addTo(app);
|
||||
}
|
||||
@@ -7,7 +7,8 @@ import { eq } from 'drizzle-orm';
|
||||
app.route({
|
||||
path: 'library',
|
||||
key: 'setAllUnused',
|
||||
description: '将所有问题设置为未使用'
|
||||
description: '将所有问题设置为未使用',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const db = getDb();
|
||||
try {
|
||||
|
||||
@@ -8,7 +8,8 @@ import { eq } from 'drizzle-orm';
|
||||
app.route({
|
||||
description: '列出问题库',
|
||||
path: 'library',
|
||||
key: 'list'
|
||||
key: 'list',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const query = ctx.query;
|
||||
const page = query.page ?? 1;
|
||||
@@ -47,7 +48,8 @@ app.route({
|
||||
app.route({
|
||||
description: '更新问题库',
|
||||
path: 'library',
|
||||
key: 'update'
|
||||
key: 'update',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const query = ctx.query;
|
||||
const id = query.id;
|
||||
@@ -104,7 +106,8 @@ app.route({
|
||||
app.route({
|
||||
description: '删除问题库',
|
||||
path: 'library',
|
||||
key: 'delete'
|
||||
key: 'delete',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const query = ctx.query;
|
||||
const id = query.id;
|
||||
@@ -130,7 +133,8 @@ app.route({
|
||||
app.route({
|
||||
description: '获取问题库详情',
|
||||
path: 'library',
|
||||
key: 'detail'
|
||||
key: 'detail',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const query = ctx.query;
|
||||
const id = query.id;
|
||||
|
||||
93
backend/zip.ts
Normal file
93
backend/zip.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import archiver from 'archiver';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import * as pkgs from './package.json';
|
||||
/**
|
||||
* 创建 ZIP 压缩包
|
||||
* @param outputPath - 输出的 zip 文件路径
|
||||
* @param files - 要压缩的文件列表
|
||||
* @param directories - 要压缩的目录列表
|
||||
*/
|
||||
export async function createZip(
|
||||
outputPath: string,
|
||||
files: string[] = [],
|
||||
directories: string[] = []
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 创建输出流
|
||||
const output = fs.createWriteStream(outputPath);
|
||||
const archive = archiver('zip', {
|
||||
zlib: { level: 9 } // 最高压缩级别
|
||||
});
|
||||
|
||||
// 监听完成事件
|
||||
output.on('close', () => {
|
||||
console.log(`✅ 压缩完成: ${archive.pointer()} bytes`);
|
||||
console.log(`📦 输出文件: ${outputPath}`);
|
||||
resolve();
|
||||
});
|
||||
|
||||
// 监听警告
|
||||
archive.on('warning', (err) => {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.warn('⚠️ 警告:', err);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听错误
|
||||
archive.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
// 连接输出流
|
||||
archive.pipe(output);
|
||||
|
||||
// 添加文件
|
||||
for (const file of files) {
|
||||
if (fs.existsSync(file)) {
|
||||
archive.file(file, { name: path.basename(file) });
|
||||
console.log(`📄 添加文件: ${file}`);
|
||||
} else {
|
||||
console.warn(`⚠️ 文件不存在: ${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加目录
|
||||
for (const dir of directories) {
|
||||
if (fs.existsSync(dir)) {
|
||||
archive.directory(dir, path.basename(dir));
|
||||
console.log(`📁 添加目录: ${dir}`);
|
||||
} else {
|
||||
console.warn(`⚠️ 目录不存在: ${dir}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 完成归档
|
||||
archive.finalize();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 为项目创建发布包
|
||||
*/
|
||||
export async function createReleaseZip(): Promise<void> {
|
||||
const files = [
|
||||
'daily-question.exe',
|
||||
'readme.md'
|
||||
];
|
||||
|
||||
const directories = [
|
||||
'public'
|
||||
];
|
||||
|
||||
await createZip(`daily-question-${pkgs.version}.zip`, files, directories);
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (import.meta.main) {
|
||||
createReleaseZip()
|
||||
.then(() => console.log('🎉 打包成功!'))
|
||||
.catch((err) => console.error('❌ 打包失败:', err));
|
||||
}
|
||||
Reference in New Issue
Block a user