根据以下 Python 代码,用 NextJS 实现相同功能,给我 TS 代码,给每个 TS 文件命名并告知放在哪里,给我实现需求的正确代码和必要说明。
import os
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# 配置 SQLite 数据库路径
basedir = os.path.abspath(os.path.dirname(__file__))
db_path = os.path.join(basedir, '../../datas/db_planfit_res-20230719.db')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + db_path
db = SQLAlchemy(app)
# 定义 Resource 数据模型
class Resource(db.Model):
__tablename__ = 'tb_res_zh'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
part_name = db.Column(db.String, nullable=False)
model_id = db.Column(db.String, nullable=False)
item_name = db.Column(db.String, nullable=False)
tag_text = db.Column(db.String)
video_url = db.Column(db.String)
content = db.Column(db.String)
def to_json(self):
return {
'id': self.id,
'part_name': self.part_name,
'model_id': self.model_id,
'item_name': self.item_name,
'tag_text': self.tag_text,
'video_url': self.video_url,
'content': self.content
}
@app.route('/resources', methods=['GET'])
def get_resources():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
resources = Resource.query.limit(per_page).offset((page - 1) * per_page).all()
return jsonify([resource.to_json() for resource in resources])
@app.route('/resources/<string:model_id>', methods=['GET'])
def get_resource(model_id):
resource = Resource.query.filter_by(model_id=model_id).first()
if resource is None:
return jsonify({'message': 'Resource not found'})
return jsonify(resource.to_json())
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9001, debug=True)
.env
位于项目根目录,db_planfit_res-20230719.db
应该位于哪里?
prisma.ts
中的prisma
编译标红提示:
Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.ts(7017)
什么意思?怎么解决?
resources/index.ts
:
import { NextApiRequest, NextApiResponse } from 'next'
import prisma from '../../../lib/prisma'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'GET') {
console.log('---Call resources!')
const page = Number(req.query.page) || 1
const perPage = Number(req.query.per_page) || 10
try {
const resources = await prisma.resource.findMany({
take: perPage,
skip: (page - 1) * perPage,
})
console.log('---Database resources:', resources)
res.status(200).json(resources)
} catch (error) {
console.error('---Database error:', error)
res.status(500).json({ message: 'Error fetching resources' })
}
} else {
res.setHeader('Allow', ['GET'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}
getResources.ts
:
export interface ResourceRaw {
content: string
id: number
item_name: string
model_id: string
part_name: string
tag_text: string
video_url: string
}
export interface Content {
title: string
text_list: TextList[]
}
interface TextList {
description: string
order_list: string[]
}
export type Resource = ReturnAsyncType<typeof getResourceList>[number]
export async function getResourceList({page, pageSize}: {page: number, pageSize: number}) {
const res = await fetch(process.env.BASE_URL + "/resources" + `?page=${page}&perPage=${pageSize}`);
console.log('---res:',res);
const data = await res.json() as ResourceRaw[];
return data.map((resource) => {
let content = [] as Content[];
try {
console.log('---resource.content:',resource.content);
content = JSON.parse(resource.content);
} catch (e) {
console.log('---JSON parse error:',e);
}
return {
...resource,
content,
}
})
}
运行报错:
---res: Response {
[Symbol(realm)]: null,
[Symbol(state)]: {
aborted: false,
rangeRequested: false,
timingAllowPassed: true,
requestIncludesCredentials: true,
type: 'default',
status: 404,
timingInfo: {
startTime: 817032.7932090759,
redirectStartTime: 0,
redirectEndTime: 0,
postRedirectStartTime: 817032.7932090759,
finalServiceWorkerStartTime: 0,
finalNetworkResponseStartTime: 0,
finalNetworkRequestStartTime: 0,
endTime: 0,
encodedBodySize: 916,
decodedBodySize: 0,
finalConnectionTimingInfo: null
},
cacheState: '',
statusText: 'Not Found',
headersList: HeadersList {
cookies: null,
[Symbol(headers map)]: [Map],
[Symbol(headers map sorted)]: null
},
urlList: [ [URL] ],
body: { stream: undefined, length: undefined, source: undefined }
},
[Symbol(headers)]: HeadersList {
cookies: null,
[Symbol(headers map)]: Map(10) {
'cache-control' => [Object],
'x-powered-by' => [Object],
'etag' => [Object],
'content-type' => [Object],
'vary' => [Object],
'date' => [Object],
'keep-alive' => [Object],
'content-encoding' => [Object],
'connection' => [Object],
'transfer-encoding' => [Object]
},
[Symbol(headers map sorted)]: null
}
}
- error SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
at JSON.parse (<anonymous>)
at AsyncResource.runInAsyncScope (node:async_hooks:203:9)
- error SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
at JSON.parse (<anonymous>)
at AsyncResource.runInAsyncScope (node:async_hooks:203:9)
什么意思?怎么解决?
写一个 CURL 测试http://127.0.0.1:3001/api/resources
NextJS 项目如何新增 API?我在项目跟目录新增了/app/api/resources/index.ts
,为什么curl -X GET "http://127.0.0.1:3001/api/resources?page=1&per_page=10"
返回 404?
resources/index.ts
import { NextApiRequest, NextApiResponse } from 'next'
import prisma from '../../../lib/prisma'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'GET') {
console.log('---Call resources!')
const page = Number(req.query.page) || 1
const perPage = Number(req.query.per_page) || 10
try {
const resources = await prisma.resource.findMany({
take: perPage,
skip: (page - 1) * perPage,
})
console.log('---Database resources:', resources)
res.status(200).json(resources)
} catch (error) {
console.error('---Database error:', error)
res.status(500).json({ message: 'Error fetching resources' })
}
} else {
res.setHeader('Allow', ['GET'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}
怎么排查和解决?给我具体的步骤和说明。
使用 NextJS App Router,给我写一个 helloworld API 示例代码。
import { respData, respErr } from "@/lib/resp";
import prisma from "../../../lib/prisma";
export async function POST(req: Request) {
try {
const body = await req.json();
console.log(`---resources req=`, body);
let { page, per_page } = body;
if (page == 0) {
page = 1;
}
if (per_page == 0) {
per_page = 10;
}
const resources = await prisma.resource.findMany({
take: per_page,
skip: (page - 1) * per_page,
});
console.log("---resources:", resources);
return respData(resources);
} catch (e) {
console.error("resources failed: ", e);
return respErr("resources failed");
}
}
curl -X POST http://localhost:3001/api/resources \
-H "Content-Type: application/json" \
-d '{"page": 1, "per_page": 10}'
运行报错:
---resources req= { page: 1, per_page: 10 }
resources failed: PrismaClientInitializationError:
Invalid `prisma.resource.findMany()` invocation:
Error querying the database: unable to open database file: /Users/kevin/1-GR个人/16-XMDM项目代码/163-TruthAIOrg/truth-ai-fitness-frontend/prisma/./datas/db_planfit_res-20230719.db
at zr.handleRequestError (/Users/kevin/1-GR个人/16-XMDM项目代码/163-TruthAIOrg/truth-ai-fitness-frontend/node_modules/.pnpm/@[email protected][email protected]/node_modules/@prisma/client/runtime/library.js:122:8581)
at zr.handleAndLogRequestError (/Users/kevin/1-GR个人/16-XMDM项目代码/163-TruthAIOrg/truth-ai-fitness-frontend/node_modules/.pnpm/@[email protected][email protected]/node_modules/@prisma/client/runtime/library.js:122:7697)
at zr.request (/Users/kevin/1-GR个人/16-XMDM项目代码/163-TruthAIOrg/truth-ai-fitness-frontend/node_modules/.pnpm/@[email protected][email protected]/node_modules/@prisma/client/runtime/library.js:122:7307)
at async POST (webpack-internal:///(sc_server)/./app/api/resources/route.ts:20:27)
at async eval (webpack-internal:///(sc_server)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/server/future/route-modules/app-route/module.js:249:37) {
clientVersion: '5.0.0',
errorCode: undefined
}
什么意思?怎么解决?
schema.prisma
在项目根目录/prisma
中,
db_planfit_res-20230719.db
在项目根目录下/datas/
中,
.env
中的写的是DATABASE_URL="file:./datas/db_planfit_res-20230719.db"
,
schema.prisma
:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
prisma.ts
import { PrismaClient } from '@prisma/client'
// 添加全局类型声明
declare global {
var prisma: PrismaClient | undefined
}
let prisma: PrismaClient
if (process.env.NODE_ENV === 'production') {
prisma = new PrismaClient()
} else {
if (!global.prisma) {
global.prisma = new PrismaClient()
}
prisma = global.prisma
}
export default prisma
> ls -alh
total 5624
drwxr-xr-x 3 kevin staff 96B Jul 29 16:54 .
drwxr-xr-x 30 kevin staff 960B Jul 29 16:54 ..
-rw-r--r-- 1 kevin staff 2.7M Jul 29 16:54 db_planfit_res-20230719.db
运行报错:
---resources req= { page: 1, per_page: 10 }
resources failed: PrismaClientInitializationError:
Invalid `prisma.resource.findMany()` invocation:
Error querying the database: unable to open database file: /Users/kevin/1-GR个人/16-XMDM项目代码/163-TruthAIOrg/truth-ai-fitness-frontend/prisma/./datas/db_planfit_res-20230719.db
at zr.handleRequestError (/Users/kevin/1-GR个人/16-XMDM项目代码/163-TruthAIOrg/truth-ai-fitness-frontend/node_modules/.pnpm/@[email protected][email protected]/node_modules/@prisma/client/runtime/library.js:122:8581)
at zr.handleAndLogRequestError (/Users/kevin/1-GR个人/16-XMDM项目代码/163-TruthAIOrg/truth-ai-fitness-frontend/node_modules/.pnpm/@[email protected][email protected]/node_modules/@prisma/client/runtime/library.js:122:7697)
at zr.request (/Users/kevin/1-GR个人/16-XMDM项目代码/163-TruthAIOrg/truth-ai-fitness-frontend/node_modules/.pnpm/@[email protected][email protected]/node_modules/@prisma/client/runtime/library.js:122:7307)
at async POST (webpack-internal:///(sc_server)/./app/api/resources/route.ts:20:27)
at async eval (webpack-internal:///(sc_server)/./node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/server/future/route-modules/app-route/module.js:249:37) {
clientVersion: '5.0.0',
errorCode: undefined
}
什么意思?怎么解决?
不要使用绝对路径,如果部署在远程服务器或者 Vercel 云平台呢?
route.ts
import { respData, respErr } from "@/lib/resp";
import prisma from "../../../lib/prisma";
export async function POST(req: Request) {
try {
const body = await req.json();
console.log(`---resources req=`, body);
let { page, per_page } = body;
if (page == 0) {
page = 1;
}
if (per_page == 0) {
per_page = 10;
}
const resources = await prisma.resource.findMany({
take: per_page,
skip: (page - 1) * per_page,
});
console.log("---resources:", resources);
return respData(resources);
} catch (e) {
console.error("resources failed: ", e);
return respErr("resources failed");
}
}
将以下代码改为调用 POST API 方式。
getResources.ts
:
export async function getResourceList({page, pageSize}: {page: number, pageSize: number}) {
const res = await fetch(process.env.BASE_URL + "/resources" + `?page=${page}&perPage=${pageSize}`);
console.log('---res:',res);
if (!res.ok) {
console.error('Failed to fetch:', res.status, res.statusText);
throw new Error('Failed to fetch resources');
}
const data = await res.json() as ResourceRaw[];
return data.map((resource) => {
let content = [] as Content[];
try {
console.log('---resource.content:',resource.content);
content = JSON.parse(resource.content);
} catch (e) {
console.log('---JSON parse error:',e);
}
return {
...resource,
content,
}
})
}
export async function getResourceList({page, pageSize}: {page: number, pageSize: number}) {
const res = await fetch(process.env.BASE_URL + "/resources", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ page, per_page: pageSize })
});
console.log('---res:', res);
if (!res.ok) {
console.error('Failed to fetch:', res.status, res.statusText);
throw new Error('Failed to fetch resources');
}
const data = await res.json() as ResourceRaw[];
return data.map((resource) => {
let content = [] as Content[];
try {
console.log('---resource.content:', resource.content);
content = JSON.parse(resource.content);
} catch (e) {
console.log('---JSON parse error:', e);
}
return {
...resource,
content,
}
});
}
运行报错:
Unhandled Runtime Error
Error: data.map is not a function
返回结构:
{
"code": 0,
"message": "ok",
"data": [
{
"id": 1,
"part_name": "背部",
"model_id": "1001",
"item_name": "硬拉",
"tag_text": "全身",
"video_url": "http://aifit.ifree258.top/training-videos/1001.mp4",
"content": "[\n {\n \"title\": \"硬拉\",\n \"text_list\": [\n {\n \"description\": \"全身\",\n \"order_list\": []\n }\n ]\n },\n {\n \"title\": \"教练的评论\",\n \"text_list\": [\n {\n \"description\": \"这对你背部肌肉的发展是一个很好的锻炼,因为你身体后部的肌肉在承受重物时可以高度参与。如果你想拥有完美的背部肌肉,这是一项至关重要的锻炼!\",\n \"order_list\": []\n }\n ]\n },\n {\n \"title\": \"锻炼指南\",\n \"text_list\": [\n {\n \"description\": \"开始的姿势\",\n \"order_list\": [\n \"1. 双脚分开与臀部同宽站立,杠铃放在脚前的地面上。\",\n \"2. 弯曲你的臀部和膝盖,使你的身体向杆的方向降低,同时保持背部平坦。\",\n \"3.用过手握杠铃,距离比肩宽稍宽。\",\n \"4. 胸部向前推,眼睛稍微向上看。\"\n ]\n },\n {\n \"description\": \"如何锻炼\",\n \"order_list\": [\n \"1. 深呼吸,支撑你的身体。\",\n \"2. 抬起杠铃,双脚穿过地面,伸展臀部和膝盖,直到你站直。\",\n \"3.保持杠铃靠近身体,手臂伸直。\",\n \"4. 一旦你到达顶部,保持这个位置一会儿。\"\n ]\n },\n {\n \"description\": \"呼吸控制\",\n \"order_list\": [\n \"1. 开始举重前先深呼吸。\",\n \"2. 举起杠铃时呼气。\",\n \"3.在抬举的顶端吸气。\"\n ]\n }\n ]\n },\n {\n \"title\": \"注意事项\",\n \"text_list\": [\n {\n \"description\": \"\",\n \"order_list\": [\n \"1. 在整个举重过程中,背部保持平坦,核心部位保持活动。\",\n \"2. 不要把背绕起来,也不要用力举杠铃。\",\n \"3.避免猛拉或弹跳重物离开地面。\",\n \"4. 确保使用适合你的体重。\"\n ]\n }\n ]\n }\n]"
}
]
}
怎么解决?
根据以下代码,实现需求:
- 根据
model_id
查询指定的resources
- 给我 NextJS App Router TS 代码
@app.route('/resources', methods=['GET'])
def get_resources():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
resources = Resource.query.limit(per_page).offset((page - 1) * per_page).all()
return jsonify([resource.to_json() for resource in resources])
@app.route('/resources/<string:model_id>', methods=['GET'])
def get_resource(model_id):
resource = Resource.query.filter_by(model_id=model_id).first()
if resource is None:
return jsonify({'message': 'Resource not found'})
return jsonify(resource.to_json())
app/api/resources/route.ts
:
import { respData, respErr } from "@/lib/resp";
import prisma from "../../../lib/prisma";
export async function POST(req: Request) {
try {
const body = await req.json();
console.log(`---resources req=`, body);
let { page, per_page } = body;
if (page == 0) {
page = 1;
}
if (per_page == 0) {
per_page = 10;
}
const resources = await prisma.resource.findMany({
take: per_page,
skip: (page - 1) * per_page,
});
console.log("---resources:", resources);
return respData(resources);
} catch (e) {
console.error("resources failed: ", e);
return respErr("resources failed");
}
}