Add SMS verification auth flow
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 5s
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 5s
This commit is contained in:
parent
c3f616dc22
commit
e8f381f73a
5
.env.example
Normal file
5
.env.example
Normal file
@ -0,0 +1,5 @@
|
||||
SMS_ACCESS_KEY_ID=
|
||||
SMS_ACCESS_KEY_SECRET=
|
||||
SMS_SIGN_NAME=广州气元科技
|
||||
SMS_TEMPLATE_CODE=SMS_506210397
|
||||
SMS_REGION=cn-hangzhou
|
||||
@ -32,6 +32,10 @@ jobs:
|
||||
echo "DOMAIN_APP is still toonflow.example.com. Replace it with the real domain before deploying."
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "${{ secrets.SMS_ACCESS_KEY_ID }}" ] || [ -z "${{ secrets.SMS_ACCESS_KEY_SECRET }}" ]; then
|
||||
echo "SMS_ACCESS_KEY_ID and SMS_ACCESS_KEY_SECRET must be configured as repository secrets."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Login to Volcano Engine CR
|
||||
run: |
|
||||
@ -112,6 +116,11 @@ jobs:
|
||||
--docker-password="${{ env.CR_PASSWORD_ACTIVE }}" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
kubectl create secret generic toonflow-sms-secret \
|
||||
--from-literal=SMS_ACCESS_KEY_ID='${{ secrets.SMS_ACCESS_KEY_ID }}' \
|
||||
--from-literal=SMS_ACCESS_KEY_SECRET='${{ secrets.SMS_ACCESS_KEY_SECRET }}' \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
kubectl apply -f k8s/cert-manager-issuer.yaml
|
||||
kubectl apply -f k8s/redirect-https-middleware.yaml
|
||||
kubectl apply -f k8s/toonflow-pvc.yaml
|
||||
|
||||
2568
data/serve/app.js
2568
data/serve/app.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -48,6 +48,22 @@ spec:
|
||||
value: "10588"
|
||||
- name: ossURL
|
||||
value: "https://videoflow.airlabs.art"
|
||||
- name: SMS_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: toonflow-sms-secret
|
||||
key: SMS_ACCESS_KEY_ID
|
||||
- name: SMS_ACCESS_KEY_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: toonflow-sms-secret
|
||||
key: SMS_ACCESS_KEY_SECRET
|
||||
- name: SMS_SIGN_NAME
|
||||
value: "广州气元科技"
|
||||
- name: SMS_TEMPLATE_CODE
|
||||
value: "SMS_506210397"
|
||||
- name: SMS_REGION
|
||||
value: "cn-hangzhou"
|
||||
volumeMounts:
|
||||
- name: toonflow-data
|
||||
mountPath: /app/data/db2.sqlite
|
||||
|
||||
38
src/app.ts
38
src/app.ts
@ -11,9 +11,10 @@ import buildRoute from "@/core";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import u from "@/utils";
|
||||
import jwt from "jsonwebtoken";
|
||||
import socketInit from "@/socket/index";
|
||||
import { isEletron } from "@/utils/getPath";
|
||||
import { normalizeBearerToken, verifyAuthToken } from "@/lib/auth";
|
||||
import { requestHasProjectAccess } from "@/lib/workspaceAccess";
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
@ -98,23 +99,34 @@ export default async function startServe(randomPort: Boolean = false) {
|
||||
}
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
const setting = await u.db("o_setting").where("key", "tokenKey").select("value").first();
|
||||
if (!setting) return res.status(444).send({ message: "服务器秘钥未配置,请联系管理员" });
|
||||
const { value: tokenKey } = setting;
|
||||
// 白名单路径
|
||||
if (
|
||||
req.path === "/api/login/login" ||
|
||||
req.path === "/api/login/register" ||
|
||||
req.path === "/api/login/sendSmsCode" ||
|
||||
req.path === "/api/login/resetPassword"
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// 从 header 或 query 参数获取 token
|
||||
const rawToken = req.headers.authorization || (req.query.token as string) || "";
|
||||
const token = rawToken.replace("Bearer ", "");
|
||||
// 白名单路径
|
||||
if (req.path === "/api/login/login") return next();
|
||||
|
||||
const token = normalizeBearerToken(rawToken);
|
||||
if (!token) return res.status(401).send({ message: "未提供token" });
|
||||
try {
|
||||
const decoded = jwt.verify(token, tokenKey as string);
|
||||
(req as any).user = decoded;
|
||||
next();
|
||||
} catch (err) {
|
||||
|
||||
const authUser = await verifyAuthToken(rawToken);
|
||||
if (!authUser) {
|
||||
return res.status(401).send({ message: "无效的token" });
|
||||
}
|
||||
|
||||
(req as any).user = authUser;
|
||||
|
||||
const canAccessProject = await requestHasProjectAccess(req, authUser.id);
|
||||
if (!canAccessProject) {
|
||||
return res.status(403).send({ message: "无权访问该项目" });
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
const router = await import("@/router");
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config({ path: ".env.local" });
|
||||
dotenv.config();
|
||||
|
||||
// 判断是否为打包后的 Electron 环境
|
||||
const isElectron = typeof process.versions?.electron !== "undefined";
|
||||
let isPackaged = false;
|
||||
|
||||
66
src/lib/auth.ts
Normal file
66
src/lib/auth.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { Request } from "express";
|
||||
import jwt from "jsonwebtoken";
|
||||
import u from "@/utils";
|
||||
|
||||
export interface AuthUser {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function getCurrentUser(req: Request): AuthUser {
|
||||
const user = (req as any).user as Partial<AuthUser> | undefined;
|
||||
const id = Number(user?.id);
|
||||
if (!Number.isFinite(id)) throw new Error("未登录");
|
||||
return {
|
||||
id,
|
||||
name: String(user?.name ?? ""),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getTokenKey(): Promise<string | null> {
|
||||
const tokenData = await u.db("o_setting").where("key", "tokenKey").first();
|
||||
return (tokenData?.value as string | undefined) ?? null;
|
||||
}
|
||||
|
||||
export function createAuthToken(user: AuthUser, secret: string): string {
|
||||
return jwt.sign({ id: user.id, name: user.name }, secret, { expiresIn: "180Days" });
|
||||
}
|
||||
|
||||
export function normalizeBearerToken(rawToken?: string | string[] | null): string {
|
||||
const token = Array.isArray(rawToken) ? rawToken[0] : rawToken;
|
||||
return (token || "").replace("Bearer ", "");
|
||||
}
|
||||
|
||||
export async function verifyAuthToken(rawToken?: string | string[] | null): Promise<AuthUser | null> {
|
||||
const secret = await getTokenKey();
|
||||
if (!secret) return null;
|
||||
|
||||
const token = normalizeBearerToken(rawToken);
|
||||
if (!token) return null;
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, secret) as Partial<AuthUser>;
|
||||
const id = Number(decoded.id);
|
||||
if (!Number.isFinite(id)) return null;
|
||||
return {
|
||||
id,
|
||||
name: String(decoded.name ?? ""),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function publicUser(user: { id?: number | null; name?: string | null; phone?: string | null }) {
|
||||
return {
|
||||
id: Number(user.id),
|
||||
name: user.name ?? "",
|
||||
phone: user.phone ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
export async function assertProjectAccess(projectId: number, userId: number): Promise<boolean> {
|
||||
if (!Number.isFinite(projectId) || !Number.isFinite(userId)) return false;
|
||||
const project = await u.db("o_project").where({ id: projectId, userId }).select("id").first();
|
||||
return Boolean(project);
|
||||
}
|
||||
@ -5,6 +5,7 @@ import { Knex } from "knex";
|
||||
import db from "@/utils/db";
|
||||
import { transform } from "sucrase";
|
||||
import rawVendorData from "./vendor.json";
|
||||
import { hashPassword, isHashedPassword } from "@/lib/password";
|
||||
|
||||
const vendorData = rawVendorData as Record<string, string>;
|
||||
|
||||
@ -57,6 +58,52 @@ export default async (knex: Knex): Promise<void> => {
|
||||
errorReason: "软件退出导致失败",
|
||||
});
|
||||
|
||||
if (await knex.schema.hasTable("o_user")) {
|
||||
await addColumn("o_user", "phone", "text");
|
||||
const adminUser = await db("o_user").where("id", 1).first();
|
||||
if (!adminUser) {
|
||||
await db("o_user").insert({ id: 1, name: "admin", password: await hashPassword("admin123") });
|
||||
}
|
||||
|
||||
const users = await db("o_user").select("id", "password");
|
||||
for (const user of users) {
|
||||
if (user.password && !isHashedPassword(user.password)) {
|
||||
await db("o_user").where("id", user.id).update({ password: await hashPassword(user.password) });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await knex.raw("CREATE UNIQUE INDEX IF NOT EXISTS idx_o_user_name_unique ON o_user(name)");
|
||||
await knex.raw("CREATE UNIQUE INDEX IF NOT EXISTS idx_o_user_phone_unique ON o_user(phone) WHERE phone IS NOT NULL AND phone != ''");
|
||||
} catch (err) {
|
||||
console.warn("[DB] 创建用户唯一索引失败:", u.error(err).message);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(await knex.schema.hasTable("o_smsCode"))) {
|
||||
await knex.schema.createTable("o_smsCode", (table) => {
|
||||
table.integer("id").notNullable();
|
||||
table.text("phone").notNullable();
|
||||
table.text("purpose").notNullable();
|
||||
table.text("codeHash").notNullable();
|
||||
table.integer("expiresAt").notNullable();
|
||||
table.integer("used").defaultTo(0);
|
||||
table.integer("attempts").defaultTo(0);
|
||||
table.integer("createTime").notNullable();
|
||||
table.integer("sentAt").notNullable();
|
||||
table.primary(["id"]);
|
||||
table.unique(["id"]);
|
||||
});
|
||||
}
|
||||
|
||||
await addColumn("o_project", "userId", "integer");
|
||||
if ((await knex.schema.hasTable("o_project")) && (await knex.schema.hasColumn("o_project", "userId"))) {
|
||||
await db("o_project")
|
||||
.whereNull("userId")
|
||||
.orWhere("userId", 0)
|
||||
.update({ userId: 1 });
|
||||
}
|
||||
|
||||
// 添加新字段
|
||||
await addColumn("o_prompt", "useData", "text");
|
||||
// 添加新字段
|
||||
|
||||
@ -16,14 +16,34 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
builder: (table) => {
|
||||
table.integer("id").notNullable();
|
||||
table.text("name");
|
||||
table.text("phone");
|
||||
table.text("password");
|
||||
table.primary(["id"]);
|
||||
table.unique(["id"]);
|
||||
table.unique(["name"]);
|
||||
table.unique(["phone"]);
|
||||
},
|
||||
initData: async (knex) => {
|
||||
await knex("o_user").insert([{ id: 1, name: "admin", password: "admin123" }]);
|
||||
},
|
||||
},
|
||||
// 短信验证码表
|
||||
{
|
||||
name: "o_smsCode",
|
||||
builder: (table) => {
|
||||
table.integer("id").notNullable();
|
||||
table.text("phone").notNullable();
|
||||
table.text("purpose").notNullable();
|
||||
table.text("codeHash").notNullable();
|
||||
table.integer("expiresAt").notNullable();
|
||||
table.integer("used").defaultTo(0);
|
||||
table.integer("attempts").defaultTo(0);
|
||||
table.integer("createTime").notNullable();
|
||||
table.integer("sentAt").notNullable();
|
||||
table.primary(["id"]);
|
||||
table.unique(["id"]);
|
||||
},
|
||||
},
|
||||
//项目表
|
||||
{
|
||||
name: "o_project",
|
||||
|
||||
30
src/lib/password.ts
Normal file
30
src/lib/password.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import crypto from "crypto";
|
||||
import { promisify } from "util";
|
||||
|
||||
const scryptAsync = promisify(crypto.scrypt);
|
||||
const PASSWORD_PREFIX = "scrypt";
|
||||
const KEY_LENGTH = 64;
|
||||
|
||||
export function isHashedPassword(password?: string | null): boolean {
|
||||
return Boolean(password?.startsWith(`${PASSWORD_PREFIX}$`));
|
||||
}
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
const salt = crypto.randomBytes(16).toString("hex");
|
||||
const key = (await scryptAsync(password, salt, KEY_LENGTH)) as Buffer;
|
||||
return `${PASSWORD_PREFIX}$${salt}$${key.toString("hex")}`;
|
||||
}
|
||||
|
||||
export async function verifyPassword(input: string, stored?: string | null): Promise<boolean> {
|
||||
if (!stored) return false;
|
||||
if (!isHashedPassword(stored)) return input === stored;
|
||||
|
||||
const [, salt, storedKey] = stored.split("$");
|
||||
if (!salt || !storedKey) return false;
|
||||
|
||||
const inputKey = (await scryptAsync(input, salt, KEY_LENGTH)) as Buffer;
|
||||
const storedBuffer = Buffer.from(storedKey, "hex");
|
||||
if (inputKey.length !== storedBuffer.length) return false;
|
||||
|
||||
return crypto.timingSafeEqual(inputKey, storedBuffer);
|
||||
}
|
||||
73
src/lib/sms.ts
Normal file
73
src/lib/sms.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import axios from "axios";
|
||||
import crypto from "crypto";
|
||||
|
||||
const endpoint = "https://dysmsapi.aliyuncs.com/";
|
||||
|
||||
function percentEncode(value: string) {
|
||||
return encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
||||
}
|
||||
|
||||
function getSmsConfig() {
|
||||
return {
|
||||
accessKeyId: process.env.SMS_ACCESS_KEY_ID || process.env.ALIBABA_CLOUD_ACCESS_KEY_ID || "",
|
||||
accessKeySecret: process.env.SMS_ACCESS_KEY_SECRET || process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET || "",
|
||||
signName: process.env.SMS_SIGN_NAME || "",
|
||||
templateCode: process.env.SMS_TEMPLATE_CODE || "",
|
||||
region: process.env.SMS_REGION || "cn-hangzhou",
|
||||
};
|
||||
}
|
||||
|
||||
function signQuery(params: Record<string, string>, accessKeySecret: string) {
|
||||
const canonicalizedQuery = Object.keys(params)
|
||||
.sort()
|
||||
.map((key) => `${percentEncode(key)}=${percentEncode(params[key])}`)
|
||||
.join("&");
|
||||
const stringToSign = `GET&${percentEncode("/")}&${percentEncode(canonicalizedQuery)}`;
|
||||
return crypto
|
||||
.createHmac("sha1", `${accessKeySecret}&`)
|
||||
.update(stringToSign)
|
||||
.digest("base64");
|
||||
}
|
||||
|
||||
export function maskPhone(phone: string) {
|
||||
return phone.replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2");
|
||||
}
|
||||
|
||||
export function isValidMainlandPhone(phone: string) {
|
||||
return /^1[3-9]\d{9}$/.test(phone);
|
||||
}
|
||||
|
||||
export async function sendSmsCode(phone: string, code: string) {
|
||||
const config = getSmsConfig();
|
||||
if (!config.accessKeyId || !config.accessKeySecret || !config.signName || !config.templateCode) {
|
||||
throw new Error("短信服务未配置");
|
||||
}
|
||||
|
||||
const params: Record<string, string> = {
|
||||
AccessKeyId: config.accessKeyId,
|
||||
Action: "SendSms",
|
||||
Format: "JSON",
|
||||
PhoneNumbers: phone,
|
||||
RegionId: config.region,
|
||||
SignatureMethod: "HMAC-SHA1",
|
||||
SignatureNonce: crypto.randomUUID(),
|
||||
SignatureVersion: "1.0",
|
||||
SignName: config.signName,
|
||||
TemplateCode: config.templateCode,
|
||||
TemplateParam: JSON.stringify({ code }),
|
||||
Timestamp: new Date().toISOString(),
|
||||
Version: "2017-05-25",
|
||||
};
|
||||
|
||||
const signature = signQuery(params, config.accessKeySecret);
|
||||
const query = Object.entries({ ...params, Signature: signature })
|
||||
.map(([key, value]) => `${percentEncode(key)}=${percentEncode(value)}`)
|
||||
.join("&");
|
||||
const { data } = await axios.get(`${endpoint}?${query}`, { timeout: 10000 });
|
||||
|
||||
if (data?.Code !== "OK") {
|
||||
throw new Error(data?.Message || data?.Code || "短信发送失败");
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
47
src/lib/smsCode.ts
Normal file
47
src/lib/smsCode.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import u from "@/utils";
|
||||
import { hashPassword, verifyPassword } from "@/lib/password";
|
||||
|
||||
export type SmsPurpose = "register" | "resetPassword";
|
||||
|
||||
const EXPIRES_IN_MS = 5 * 60 * 1000;
|
||||
const RESEND_INTERVAL_MS = 60 * 1000;
|
||||
const MAX_ATTEMPTS = 5;
|
||||
|
||||
export function createNumericCode() {
|
||||
return String(Math.floor(100000 + Math.random() * 900000));
|
||||
}
|
||||
|
||||
export async function assertCanSendSmsCode(phone: string, purpose: SmsPurpose) {
|
||||
const latest = await u.db("o_smsCode").where({ phone, purpose, used: 0 }).orderBy("createTime", "desc").first();
|
||||
if (latest?.sentAt && Date.now() - latest.sentAt < RESEND_INTERVAL_MS) {
|
||||
const waitSeconds = Math.ceil((RESEND_INTERVAL_MS - (Date.now() - latest.sentAt)) / 1000);
|
||||
throw new Error(`验证码发送过于频繁,请 ${waitSeconds} 秒后再试`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveSmsCode(phone: string, purpose: SmsPurpose, code: string) {
|
||||
await u.db("o_smsCode").insert({
|
||||
id: Date.now(),
|
||||
phone,
|
||||
purpose,
|
||||
codeHash: await hashPassword(code),
|
||||
expiresAt: Date.now() + EXPIRES_IN_MS,
|
||||
used: 0,
|
||||
attempts: 0,
|
||||
createTime: Date.now(),
|
||||
sentAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
export async function verifySmsCode(phone: string, purpose: SmsPurpose, code: string) {
|
||||
const record = await u.db("o_smsCode").where({ phone, purpose, used: 0 }).orderBy("createTime", "desc").first();
|
||||
if (!record) throw new Error("验证码不存在或已失效");
|
||||
if ((record.attempts ?? 0) >= MAX_ATTEMPTS) throw new Error("验证码尝试次数过多,请重新获取");
|
||||
if ((record.expiresAt ?? 0) < Date.now()) throw new Error("验证码已过期");
|
||||
|
||||
await u.db("o_smsCode").where("id", record.id).update({ attempts: (record.attempts ?? 0) + 1 });
|
||||
const matched = await verifyPassword(code, record.codeHash);
|
||||
if (!matched) throw new Error("验证码错误");
|
||||
|
||||
await u.db("o_smsCode").where("id", record.id).update({ used: 1 });
|
||||
}
|
||||
139
src/lib/workspaceAccess.ts
Normal file
139
src/lib/workspaceAccess.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { Request } from "express";
|
||||
import u from "@/utils";
|
||||
import { assertProjectAccess } from "@/lib/auth";
|
||||
|
||||
type ProjectIdLookup = (req: Request) => Promise<Array<number | null | undefined>>;
|
||||
|
||||
function toNumber(value: unknown): number | null {
|
||||
const numberValue = Number(value);
|
||||
return Number.isFinite(numberValue) ? numberValue : null;
|
||||
}
|
||||
|
||||
function toNumberList(value: unknown): number[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value.map(toNumber).filter((item): item is number => item != null);
|
||||
}
|
||||
|
||||
async function projectIdsFromTable(table: string, ids: number[]) {
|
||||
ids = ids.filter(Number.isFinite);
|
||||
if (!ids.length) return [];
|
||||
return u
|
||||
.db(table as any)
|
||||
.whereIn("id", ids)
|
||||
.select("projectId")
|
||||
.then((rows: any[]) => rows.map((row) => row.projectId));
|
||||
}
|
||||
|
||||
async function projectIdsFromImage(ids: number[]) {
|
||||
ids = ids.filter(Number.isFinite);
|
||||
if (!ids.length) return [];
|
||||
return u
|
||||
.db("o_image")
|
||||
.leftJoin("o_assets", "o_assets.id", "o_image.assetsId")
|
||||
.whereIn("o_image.id", ids)
|
||||
.select("o_assets.projectId")
|
||||
.then((rows) => rows.map((row: any) => row.projectId));
|
||||
}
|
||||
|
||||
async function projectIdsFromEvents(ids: number[]) {
|
||||
ids = ids.filter(Number.isFinite);
|
||||
if (!ids.length) return [];
|
||||
return u
|
||||
.db("o_eventChapter")
|
||||
.leftJoin("o_novel", "o_novel.id", "o_eventChapter.novelId")
|
||||
.whereIn("o_eventChapter.eventId", ids)
|
||||
.select("o_novel.projectId")
|
||||
.then((rows) => rows.map((row: any) => row.projectId));
|
||||
}
|
||||
|
||||
async function projectIdsFromFlow(ids: number[]) {
|
||||
ids = ids.filter(Number.isFinite);
|
||||
if (!ids.length) return [];
|
||||
const [assetRows, storyboardRows] = await Promise.all([
|
||||
u.db("o_assets").whereIn("flowId", ids).select("projectId"),
|
||||
u.db("o_storyboard").whereIn("flowId", ids).select("projectId"),
|
||||
]);
|
||||
return [...assetRows, ...storyboardRows].map((row: any) => row.projectId);
|
||||
}
|
||||
|
||||
async function projectIdsFromWorkbenchItems(req: Request) {
|
||||
const items = Array.isArray(req.body?.items) ? req.body.items : [];
|
||||
const assetIds = items.filter((item: any) => item.sources === "assets").map((item: any) => Number(item.id));
|
||||
const storyboardIds = items.filter((item: any) => item.sources === "storyboard").map((item: any) => Number(item.id));
|
||||
const [assets, storyboards] = await Promise.all([projectIdsFromTable("o_assets", assetIds), projectIdsFromTable("o_storyboard", storyboardIds)]);
|
||||
return [...assets, ...storyboards];
|
||||
}
|
||||
|
||||
const projectIdAliases: Record<string, string[]> = {
|
||||
"/api/project/delProject": ["id"],
|
||||
"/api/project/editProject": ["id"],
|
||||
"/api/project/getSingleProject": ["id"],
|
||||
"/api/general/getSingleProject": ["id"],
|
||||
"/api/general/updateProject": ["id"],
|
||||
};
|
||||
|
||||
const resourceLookups: Record<string, ProjectIdLookup> = {
|
||||
"/api/assets/delAssets": (req) => projectIdsFromTable("o_assets", [Number(req.body?.id)]),
|
||||
"/api/assets/updateAssets": (req) => projectIdsFromTable("o_assets", [Number(req.body?.id)]),
|
||||
"/api/assets/getImage": (req) => projectIdsFromTable("o_assets", [Number(req.body?.assetsId)]),
|
||||
"/api/assets/delImage": (req) => projectIdsFromImage([Number(req.body?.id)]),
|
||||
"/api/assetsGenerate/cancelGenerate": (req) => projectIdsFromImage([Number(req.body?.id)]),
|
||||
"/api/script/delScript": (req) => projectIdsFromTable("o_script", toNumberList(req.body?.ids)),
|
||||
"/api/script/exportScript": (req) => projectIdsFromTable("o_script", toNumberList(req.body?.id)),
|
||||
"/api/script/pollScriptAssets": (req) => projectIdsFromTable("o_script", toNumberList(req.body?.ids)),
|
||||
"/api/script/updateScript": (req) => projectIdsFromTable("o_script", [Number(req.body?.id)]),
|
||||
"/api/novel/batchDeleteNovel": (req) => projectIdsFromTable("o_novel", toNumberList(req.body?.ids)),
|
||||
"/api/novel/delNovel": (req) => projectIdsFromTable("o_novel", [Number(req.body?.id)]),
|
||||
"/api/novel/getNovelEventState": (req) => projectIdsFromTable("o_novel", toNumberList(req.body?.ids)),
|
||||
"/api/novel/updateNovel": (req) => projectIdsFromTable("o_novel", [Number(req.body?.id)]),
|
||||
"/api/novel/event/batchDeleteEvent": (req) => projectIdsFromEvents(toNumberList(req.body?.ids)),
|
||||
"/api/novel/event/deletEvent": (req) => projectIdsFromEvents([Number(req.body?.id)]),
|
||||
"/api/production/assets/pollingImage": (req) => projectIdsFromTable("o_assets", toNumberList(req.body?.ids)),
|
||||
"/api/production/assets/updateAssetsUrl": (req) => projectIdsFromTable("o_assets", [Number(req.body?.id)]),
|
||||
"/api/production/storyboard/editStoryboardInfo": (req) => projectIdsFromTable("o_storyboard", [Number(req.body?.id)]),
|
||||
"/api/production/storyboard/pollingImage": (req) => projectIdsFromTable("o_storyboard", toNumberList(req.body?.ids)),
|
||||
"/api/production/storyboard/previewImage": (req) => projectIdsFromTable("o_storyboard", toNumberList(req.body?.storyboardIds)),
|
||||
"/api/production/storyboard/removeFrame": (req) => projectIdsFromTable("o_storyboard", [Number(req.body?.id)]),
|
||||
"/api/production/storyboard/updateStoryboardUrl": (req) => projectIdsFromTable("o_storyboard", [Number(req.body?.id)]),
|
||||
"/api/production/workbench/deleteTrack": (req) => projectIdsFromTable("o_videoTrack", [Number(req.body?.id)]),
|
||||
"/api/production/workbench/delVideo": (req) => projectIdsFromTable("o_video", [Number(req.body?.id)]),
|
||||
"/api/production/workbench/getAudioBindAssetsList": (req) => projectIdsFromTable("o_assets", toNumberList(req.body?.assetsIds)),
|
||||
"/api/production/workbench/getFileUrl": projectIdsFromWorkbenchItems,
|
||||
"/api/production/workbench/selectVideo": (req) => projectIdsFromTable("o_videoTrack", [Number(req.body?.trackId)]),
|
||||
"/api/production/workbench/updateVideoDuration": (req) => projectIdsFromTable("o_videoTrack", [Number(req.body?.id)]),
|
||||
"/api/production/workbench/updateVideoPrompt": (req) => projectIdsFromTable("o_videoTrack", [Number(req.body?.id)]),
|
||||
"/api/production/editImage/getImageFlow": (req) => projectIdsFromFlow([Number(req.body?.id)]),
|
||||
"/api/production/editImage/updateImageFlow": (req) => projectIdsFromFlow([Number(req.body?.flowId)]),
|
||||
"/api/scriptAgent/updateData": (req) => projectIdsFromTable("o_agentWorkData", [Number(req.body?.id)]),
|
||||
"/api/task/taskDetails": (req) => projectIdsFromTable("o_tasks", [Number(req.body?.taskId)]),
|
||||
};
|
||||
|
||||
async function getRequestProjectIds(req: Request): Promise<number[]> {
|
||||
const projectIds = new Set<number>();
|
||||
const directProjectId = toNumber(req.body?.projectId ?? req.query?.projectId);
|
||||
if (directProjectId != null) projectIds.add(directProjectId);
|
||||
|
||||
for (const key of projectIdAliases[req.path] ?? []) {
|
||||
const value = toNumber(req.body?.[key] ?? req.query?.[key]);
|
||||
if (value != null) projectIds.add(value);
|
||||
}
|
||||
|
||||
const lookup = resourceLookups[req.path];
|
||||
if (lookup) {
|
||||
const resolvedProjectIds = await lookup(req);
|
||||
resolvedProjectIds.forEach((projectId) => {
|
||||
const value = toNumber(projectId);
|
||||
if (value != null) projectIds.add(value);
|
||||
});
|
||||
}
|
||||
|
||||
return Array.from(projectIds);
|
||||
}
|
||||
|
||||
export async function requestHasProjectAccess(req: Request, userId: number): Promise<boolean> {
|
||||
const projectIds = await getRequestProjectIds(req);
|
||||
for (const projectId of projectIds) {
|
||||
if (!(await assertProjectAccess(projectId, userId))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
540
src/router.ts
540
src/router.ts
@ -1,4 +1,4 @@
|
||||
// @routes-hash d3a4d7955a8f63d5cc1c769612bd48c2
|
||||
// @routes-hash 3e9f14716fc04f376339fe13c7f95b40
|
||||
import { Express } from "express";
|
||||
|
||||
import route1 from "./routes/agents/clearMemory";
|
||||
@ -36,138 +36,143 @@ import route32 from "./routes/general/generalStatistics";
|
||||
import route33 from "./routes/general/getSingleProject";
|
||||
import route34 from "./routes/general/updateProject";
|
||||
import route35 from "./routes/login/login";
|
||||
import route36 from "./routes/modelSelect/getModelDetail";
|
||||
import route37 from "./routes/modelSelect/getModelList";
|
||||
import route38 from "./routes/novel/addNovel";
|
||||
import route39 from "./routes/novel/batchDeleteNovel";
|
||||
import route40 from "./routes/novel/delNovel";
|
||||
import route41 from "./routes/novel/event/batchDeleteEvent";
|
||||
import route42 from "./routes/novel/event/deletEvent";
|
||||
import route43 from "./routes/novel/event/generateEvents";
|
||||
import route44 from "./routes/novel/event/getEvent";
|
||||
import route45 from "./routes/novel/getNovel";
|
||||
import route46 from "./routes/novel/getNovelData";
|
||||
import route47 from "./routes/novel/getNovelEventState";
|
||||
import route48 from "./routes/novel/getNovelIndex";
|
||||
import route49 from "./routes/novel/updateNovel";
|
||||
import route50 from "./routes/other/deleteAllData";
|
||||
import route51 from "./routes/other/getVersion";
|
||||
import route52 from "./routes/production/assets/batchGenerateAssetsImage";
|
||||
import route53 from "./routes/production/assets/deleteAssetsDireve";
|
||||
import route54 from "./routes/production/assets/pollingImage";
|
||||
import route55 from "./routes/production/assets/updateAssetsUrl";
|
||||
import route56 from "./routes/production/editImage/generateFlowImage";
|
||||
import route57 from "./routes/production/editImage/getImageDefaultModle";
|
||||
import route58 from "./routes/production/editImage/getImageFlow";
|
||||
import route59 from "./routes/production/editImage/saveImageFlow";
|
||||
import route60 from "./routes/production/editImage/updateImageFlow";
|
||||
import route61 from "./routes/production/editImage/uploadImage";
|
||||
import route62 from "./routes/production/getFlowData";
|
||||
import route63 from "./routes/production/getStoryboardData";
|
||||
import route64 from "./routes/production/saveFlowData";
|
||||
import route65 from "./routes/production/storyboard/addStoryboard";
|
||||
import route66 from "./routes/production/storyboard/batchAddStoryboardInfo";
|
||||
import route67 from "./routes/production/storyboard/batchDelete";
|
||||
import route68 from "./routes/production/storyboard/batchGenerateImage";
|
||||
import route69 from "./routes/production/storyboard/downPreviewImage";
|
||||
import route70 from "./routes/production/storyboard/editStoryboardInfo";
|
||||
import route71 from "./routes/production/storyboard/getStoryboardData";
|
||||
import route72 from "./routes/production/storyboard/pollingImage";
|
||||
import route73 from "./routes/production/storyboard/previewImage";
|
||||
import route74 from "./routes/production/storyboard/removeFrame";
|
||||
import route75 from "./routes/production/storyboard/updateStoryboardUrl";
|
||||
import route76 from "./routes/production/workbench/addTrack";
|
||||
import route77 from "./routes/production/workbench/batchGeneratePrompt";
|
||||
import route78 from "./routes/production/workbench/batchGenerateVideo";
|
||||
import route79 from "./routes/production/workbench/checkVideoStateList";
|
||||
import route80 from "./routes/production/workbench/deleteTrack";
|
||||
import route81 from "./routes/production/workbench/delVideo";
|
||||
import route82 from "./routes/production/workbench/generateVideo";
|
||||
import route83 from "./routes/production/workbench/generateVideoPrompt";
|
||||
import route84 from "./routes/production/workbench/getAudioBindAssetsList";
|
||||
import route85 from "./routes/production/workbench/getFileUrl";
|
||||
import route86 from "./routes/production/workbench/getGenerateData";
|
||||
import route87 from "./routes/production/workbench/getVideoList";
|
||||
import route88 from "./routes/production/workbench/selectVideo";
|
||||
import route89 from "./routes/production/workbench/updateVideoDuration";
|
||||
import route90 from "./routes/production/workbench/updateVideoPrompt";
|
||||
import route91 from "./routes/project/addDirectorManual";
|
||||
import route92 from "./routes/project/addProject";
|
||||
import route93 from "./routes/project/addVisualManual";
|
||||
import route94 from "./routes/project/deleteDirectorManual";
|
||||
import route95 from "./routes/project/deleteVisualManual";
|
||||
import route96 from "./routes/project/delProject";
|
||||
import route97 from "./routes/project/editDirectorlManual";
|
||||
import route98 from "./routes/project/editProject";
|
||||
import route99 from "./routes/project/editVisualManual";
|
||||
import route100 from "./routes/project/getModelDetails";
|
||||
import route101 from "./routes/project/getProject";
|
||||
import route102 from "./routes/project/getVisualManual";
|
||||
import route103 from "./routes/project/queryDirectorManual";
|
||||
import route104 from "./routes/project/visualManual";
|
||||
import route105 from "./routes/script/addScript";
|
||||
import route106 from "./routes/script/batchAddScript";
|
||||
import route107 from "./routes/script/delScript";
|
||||
import route108 from "./routes/script/exportScript";
|
||||
import route109 from "./routes/script/extractAssets";
|
||||
import route110 from "./routes/script/getAiRegex";
|
||||
import route111 from "./routes/script/getScrptApi";
|
||||
import route112 from "./routes/script/pollScriptAssets";
|
||||
import route113 from "./routes/script/updateScript";
|
||||
import route114 from "./routes/scriptAgent/getPlanData";
|
||||
import route115 from "./routes/scriptAgent/setPlanData";
|
||||
import route116 from "./routes/scriptAgent/updateData";
|
||||
import route117 from "./routes/setting/about/checkUpdate";
|
||||
import route118 from "./routes/setting/about/downloadApp";
|
||||
import route119 from "./routes/setting/agentDeploy/agentSetKey";
|
||||
import route120 from "./routes/setting/agentDeploy/deployAgentModel";
|
||||
import route121 from "./routes/setting/agentDeploy/getAgentDeploy";
|
||||
import route122 from "./routes/setting/agentDeploy/getAgentUseMode";
|
||||
import route123 from "./routes/setting/agentDeploy/updateUseMode";
|
||||
import route124 from "./routes/setting/dbConfig/clearData";
|
||||
import route125 from "./routes/setting/dbConfig/clearTable";
|
||||
import route126 from "./routes/setting/dbConfig/dbInfo";
|
||||
import route127 from "./routes/setting/dbConfig/exportData";
|
||||
import route128 from "./routes/setting/dbConfig/importData";
|
||||
import route129 from "./routes/setting/dev/getSwitchAiDevTool";
|
||||
import route130 from "./routes/setting/dev/updateSwitchAiDevTool";
|
||||
import route131 from "./routes/setting/fileManagement/openFolder";
|
||||
import route132 from "./routes/setting/getTextModel";
|
||||
import route133 from "./routes/setting/loginConfig/getUser";
|
||||
import route134 from "./routes/setting/loginConfig/updateUserPwd";
|
||||
import route135 from "./routes/setting/memoryConfig/delAllMemory";
|
||||
import route136 from "./routes/setting/memoryConfig/getMemory";
|
||||
import route137 from "./routes/setting/memoryConfig/sureMemory";
|
||||
import route138 from "./routes/setting/modelMap/bindingPrompt";
|
||||
import route139 from "./routes/setting/modelMap/deletePrompt";
|
||||
import route140 from "./routes/setting/modelMap/getImageAndVideoModel";
|
||||
import route141 from "./routes/setting/modelMap/getPromptList";
|
||||
import route142 from "./routes/setting/modelMap/savePrompt";
|
||||
import route143 from "./routes/setting/modelMap/updatePrompt";
|
||||
import route144 from "./routes/setting/promptManage/getPrompt";
|
||||
import route145 from "./routes/setting/promptManage/updatePrompt";
|
||||
import route146 from "./routes/setting/skillManagement/getSkillContent";
|
||||
import route147 from "./routes/setting/skillManagement/getSkillList";
|
||||
import route148 from "./routes/setting/skillManagement/saveSkillContent";
|
||||
import route149 from "./routes/setting/vendorConfig/addVendor";
|
||||
import route150 from "./routes/setting/vendorConfig/addVendorModel";
|
||||
import route151 from "./routes/setting/vendorConfig/deleteVendor";
|
||||
import route152 from "./routes/setting/vendorConfig/delVendorModel";
|
||||
import route153 from "./routes/setting/vendorConfig/enableVendor";
|
||||
import route154 from "./routes/setting/vendorConfig/getCodeByLink";
|
||||
import route155 from "./routes/setting/vendorConfig/getVendorList";
|
||||
import route156 from "./routes/setting/vendorConfig/modelTest";
|
||||
import route157 from "./routes/setting/vendorConfig/modelTest/imageTest";
|
||||
import route158 from "./routes/setting/vendorConfig/modelTest/textTest";
|
||||
import route159 from "./routes/setting/vendorConfig/modelTest/videoTest";
|
||||
import route160 from "./routes/setting/vendorConfig/updateCode";
|
||||
import route161 from "./routes/setting/vendorConfig/updateVendorInputs";
|
||||
import route162 from "./routes/setting/vendorConfig/upVendorModel";
|
||||
import route163 from "./routes/task/getProject";
|
||||
import route164 from "./routes/task/getTaskApi";
|
||||
import route165 from "./routes/task/getTaskCategories";
|
||||
import route166 from "./routes/task/taskDetails";
|
||||
import route167 from "./routes/test/test";
|
||||
import route36 from "./routes/login/me";
|
||||
import route37 from "./routes/login/register";
|
||||
import route38 from "./routes/login/resetPassword";
|
||||
import route39 from "./routes/login/sendSmsCode";
|
||||
import route40 from "./routes/modelSelect/getModelDetail";
|
||||
import route41 from "./routes/modelSelect/getModelList";
|
||||
import route42 from "./routes/novel/addNovel";
|
||||
import route43 from "./routes/novel/batchDeleteNovel";
|
||||
import route44 from "./routes/novel/delNovel";
|
||||
import route45 from "./routes/novel/event/batchDeleteEvent";
|
||||
import route46 from "./routes/novel/event/deletEvent";
|
||||
import route47 from "./routes/novel/event/generateEvents";
|
||||
import route48 from "./routes/novel/event/getEvent";
|
||||
import route49 from "./routes/novel/getNovel";
|
||||
import route50 from "./routes/novel/getNovelData";
|
||||
import route51 from "./routes/novel/getNovelEventState";
|
||||
import route52 from "./routes/novel/getNovelIndex";
|
||||
import route53 from "./routes/novel/updateNovel";
|
||||
import route54 from "./routes/other/deleteAllData";
|
||||
import route55 from "./routes/other/getVersion";
|
||||
import route56 from "./routes/production/assets/batchGenerateAssetsImage";
|
||||
import route57 from "./routes/production/assets/deleteAssetsDireve";
|
||||
import route58 from "./routes/production/assets/pollingImage";
|
||||
import route59 from "./routes/production/assets/updateAssetsUrl";
|
||||
import route60 from "./routes/production/editImage/generateFlowImage";
|
||||
import route61 from "./routes/production/editImage/getImageDefaultModle";
|
||||
import route62 from "./routes/production/editImage/getImageFlow";
|
||||
import route63 from "./routes/production/editImage/saveImageFlow";
|
||||
import route64 from "./routes/production/editImage/updateImageFlow";
|
||||
import route65 from "./routes/production/editImage/uploadImage";
|
||||
import route66 from "./routes/production/getFlowData";
|
||||
import route67 from "./routes/production/getStoryboardData";
|
||||
import route68 from "./routes/production/saveFlowData";
|
||||
import route69 from "./routes/production/storyboard/addStoryboard";
|
||||
import route70 from "./routes/production/storyboard/batchAddStoryboardInfo";
|
||||
import route71 from "./routes/production/storyboard/batchDelete";
|
||||
import route72 from "./routes/production/storyboard/batchGenerateImage";
|
||||
import route73 from "./routes/production/storyboard/downPreviewImage";
|
||||
import route74 from "./routes/production/storyboard/editStoryboardInfo";
|
||||
import route75 from "./routes/production/storyboard/getStoryboardData";
|
||||
import route76 from "./routes/production/storyboard/pollingImage";
|
||||
import route77 from "./routes/production/storyboard/previewImage";
|
||||
import route78 from "./routes/production/storyboard/removeFrame";
|
||||
import route79 from "./routes/production/storyboard/updateStoryboardUrl";
|
||||
import route80 from "./routes/production/workbench/addTrack";
|
||||
import route81 from "./routes/production/workbench/batchGeneratePrompt";
|
||||
import route82 from "./routes/production/workbench/batchGenerateVideo";
|
||||
import route83 from "./routes/production/workbench/checkVideoStateList";
|
||||
import route84 from "./routes/production/workbench/deleteTrack";
|
||||
import route85 from "./routes/production/workbench/delVideo";
|
||||
import route86 from "./routes/production/workbench/generateVideo";
|
||||
import route87 from "./routes/production/workbench/generateVideoPrompt";
|
||||
import route88 from "./routes/production/workbench/getAudioBindAssetsList";
|
||||
import route89 from "./routes/production/workbench/getFileUrl";
|
||||
import route90 from "./routes/production/workbench/getGenerateData";
|
||||
import route91 from "./routes/production/workbench/getVideoList";
|
||||
import route92 from "./routes/production/workbench/selectVideo";
|
||||
import route93 from "./routes/production/workbench/updateVideoDuration";
|
||||
import route94 from "./routes/production/workbench/updateVideoPrompt";
|
||||
import route95 from "./routes/project/addDirectorManual";
|
||||
import route96 from "./routes/project/addProject";
|
||||
import route97 from "./routes/project/addVisualManual";
|
||||
import route98 from "./routes/project/deleteDirectorManual";
|
||||
import route99 from "./routes/project/deleteVisualManual";
|
||||
import route100 from "./routes/project/delProject";
|
||||
import route101 from "./routes/project/editDirectorlManual";
|
||||
import route102 from "./routes/project/editProject";
|
||||
import route103 from "./routes/project/editVisualManual";
|
||||
import route104 from "./routes/project/getModelDetails";
|
||||
import route105 from "./routes/project/getProject";
|
||||
import route106 from "./routes/project/getSingleProject";
|
||||
import route107 from "./routes/project/getVisualManual";
|
||||
import route108 from "./routes/project/queryDirectorManual";
|
||||
import route109 from "./routes/project/visualManual";
|
||||
import route110 from "./routes/script/addScript";
|
||||
import route111 from "./routes/script/batchAddScript";
|
||||
import route112 from "./routes/script/delScript";
|
||||
import route113 from "./routes/script/exportScript";
|
||||
import route114 from "./routes/script/extractAssets";
|
||||
import route115 from "./routes/script/getAiRegex";
|
||||
import route116 from "./routes/script/getScrptApi";
|
||||
import route117 from "./routes/script/pollScriptAssets";
|
||||
import route118 from "./routes/script/updateScript";
|
||||
import route119 from "./routes/scriptAgent/getPlanData";
|
||||
import route120 from "./routes/scriptAgent/setPlanData";
|
||||
import route121 from "./routes/scriptAgent/updateData";
|
||||
import route122 from "./routes/setting/about/checkUpdate";
|
||||
import route123 from "./routes/setting/about/downloadApp";
|
||||
import route124 from "./routes/setting/agentDeploy/agentSetKey";
|
||||
import route125 from "./routes/setting/agentDeploy/deployAgentModel";
|
||||
import route126 from "./routes/setting/agentDeploy/getAgentDeploy";
|
||||
import route127 from "./routes/setting/agentDeploy/getAgentUseMode";
|
||||
import route128 from "./routes/setting/agentDeploy/updateUseMode";
|
||||
import route129 from "./routes/setting/dbConfig/clearData";
|
||||
import route130 from "./routes/setting/dbConfig/clearTable";
|
||||
import route131 from "./routes/setting/dbConfig/dbInfo";
|
||||
import route132 from "./routes/setting/dbConfig/exportData";
|
||||
import route133 from "./routes/setting/dbConfig/importData";
|
||||
import route134 from "./routes/setting/dev/getSwitchAiDevTool";
|
||||
import route135 from "./routes/setting/dev/updateSwitchAiDevTool";
|
||||
import route136 from "./routes/setting/fileManagement/openFolder";
|
||||
import route137 from "./routes/setting/getTextModel";
|
||||
import route138 from "./routes/setting/loginConfig/getUser";
|
||||
import route139 from "./routes/setting/loginConfig/updateUserPwd";
|
||||
import route140 from "./routes/setting/memoryConfig/delAllMemory";
|
||||
import route141 from "./routes/setting/memoryConfig/getMemory";
|
||||
import route142 from "./routes/setting/memoryConfig/sureMemory";
|
||||
import route143 from "./routes/setting/modelMap/bindingPrompt";
|
||||
import route144 from "./routes/setting/modelMap/deletePrompt";
|
||||
import route145 from "./routes/setting/modelMap/getImageAndVideoModel";
|
||||
import route146 from "./routes/setting/modelMap/getPromptList";
|
||||
import route147 from "./routes/setting/modelMap/savePrompt";
|
||||
import route148 from "./routes/setting/modelMap/updatePrompt";
|
||||
import route149 from "./routes/setting/promptManage/getPrompt";
|
||||
import route150 from "./routes/setting/promptManage/updatePrompt";
|
||||
import route151 from "./routes/setting/skillManagement/getSkillContent";
|
||||
import route152 from "./routes/setting/skillManagement/getSkillList";
|
||||
import route153 from "./routes/setting/skillManagement/saveSkillContent";
|
||||
import route154 from "./routes/setting/vendorConfig/addVendor";
|
||||
import route155 from "./routes/setting/vendorConfig/addVendorModel";
|
||||
import route156 from "./routes/setting/vendorConfig/deleteVendor";
|
||||
import route157 from "./routes/setting/vendorConfig/delVendorModel";
|
||||
import route158 from "./routes/setting/vendorConfig/enableVendor";
|
||||
import route159 from "./routes/setting/vendorConfig/getCodeByLink";
|
||||
import route160 from "./routes/setting/vendorConfig/getVendorList";
|
||||
import route161 from "./routes/setting/vendorConfig/modelTest";
|
||||
import route162 from "./routes/setting/vendorConfig/modelTest/imageTest";
|
||||
import route163 from "./routes/setting/vendorConfig/modelTest/textTest";
|
||||
import route164 from "./routes/setting/vendorConfig/modelTest/videoTest";
|
||||
import route165 from "./routes/setting/vendorConfig/updateCode";
|
||||
import route166 from "./routes/setting/vendorConfig/updateVendorInputs";
|
||||
import route167 from "./routes/setting/vendorConfig/upVendorModel";
|
||||
import route168 from "./routes/task/getProject";
|
||||
import route169 from "./routes/task/getTaskApi";
|
||||
import route170 from "./routes/task/getTaskCategories";
|
||||
import route171 from "./routes/task/taskDetails";
|
||||
import route172 from "./routes/test/test";
|
||||
|
||||
export default async (app: Express) => {
|
||||
app.use("/api/agents/clearMemory", route1);
|
||||
@ -205,136 +210,141 @@ export default async (app: Express) => {
|
||||
app.use("/api/general/getSingleProject", route33);
|
||||
app.use("/api/general/updateProject", route34);
|
||||
app.use("/api/login/login", route35);
|
||||
app.use("/api/modelSelect/getModelDetail", route36);
|
||||
app.use("/api/modelSelect/getModelList", route37);
|
||||
app.use("/api/novel/addNovel", route38);
|
||||
app.use("/api/novel/batchDeleteNovel", route39);
|
||||
app.use("/api/novel/delNovel", route40);
|
||||
app.use("/api/novel/event/batchDeleteEvent", route41);
|
||||
app.use("/api/novel/event/deletEvent", route42);
|
||||
app.use("/api/novel/event/generateEvents", route43);
|
||||
app.use("/api/novel/event/getEvent", route44);
|
||||
app.use("/api/novel/getNovel", route45);
|
||||
app.use("/api/novel/getNovelData", route46);
|
||||
app.use("/api/novel/getNovelEventState", route47);
|
||||
app.use("/api/novel/getNovelIndex", route48);
|
||||
app.use("/api/novel/updateNovel", route49);
|
||||
app.use("/api/other/deleteAllData", route50);
|
||||
app.use("/api/other/getVersion", route51);
|
||||
app.use("/api/production/assets/batchGenerateAssetsImage", route52);
|
||||
app.use("/api/production/assets/deleteAssetsDireve", route53);
|
||||
app.use("/api/production/assets/pollingImage", route54);
|
||||
app.use("/api/production/assets/updateAssetsUrl", route55);
|
||||
app.use("/api/production/editImage/generateFlowImage", route56);
|
||||
app.use("/api/production/editImage/getImageDefaultModle", route57);
|
||||
app.use("/api/production/editImage/getImageFlow", route58);
|
||||
app.use("/api/production/editImage/saveImageFlow", route59);
|
||||
app.use("/api/production/editImage/updateImageFlow", route60);
|
||||
app.use("/api/production/editImage/uploadImage", route61);
|
||||
app.use("/api/production/getFlowData", route62);
|
||||
app.use("/api/production/getStoryboardData", route63);
|
||||
app.use("/api/production/saveFlowData", route64);
|
||||
app.use("/api/production/storyboard/addStoryboard", route65);
|
||||
app.use("/api/production/storyboard/batchAddStoryboardInfo", route66);
|
||||
app.use("/api/production/storyboard/batchDelete", route67);
|
||||
app.use("/api/production/storyboard/batchGenerateImage", route68);
|
||||
app.use("/api/production/storyboard/downPreviewImage", route69);
|
||||
app.use("/api/production/storyboard/editStoryboardInfo", route70);
|
||||
app.use("/api/production/storyboard/getStoryboardData", route71);
|
||||
app.use("/api/production/storyboard/pollingImage", route72);
|
||||
app.use("/api/production/storyboard/previewImage", route73);
|
||||
app.use("/api/production/storyboard/removeFrame", route74);
|
||||
app.use("/api/production/storyboard/updateStoryboardUrl", route75);
|
||||
app.use("/api/production/workbench/addTrack", route76);
|
||||
app.use("/api/production/workbench/batchGeneratePrompt", route77);
|
||||
app.use("/api/production/workbench/batchGenerateVideo", route78);
|
||||
app.use("/api/production/workbench/checkVideoStateList", route79);
|
||||
app.use("/api/production/workbench/deleteTrack", route80);
|
||||
app.use("/api/production/workbench/delVideo", route81);
|
||||
app.use("/api/production/workbench/generateVideo", route82);
|
||||
app.use("/api/production/workbench/generateVideoPrompt", route83);
|
||||
app.use("/api/production/workbench/getAudioBindAssetsList", route84);
|
||||
app.use("/api/production/workbench/getFileUrl", route85);
|
||||
app.use("/api/production/workbench/getGenerateData", route86);
|
||||
app.use("/api/production/workbench/getVideoList", route87);
|
||||
app.use("/api/production/workbench/selectVideo", route88);
|
||||
app.use("/api/production/workbench/updateVideoDuration", route89);
|
||||
app.use("/api/production/workbench/updateVideoPrompt", route90);
|
||||
app.use("/api/project/addDirectorManual", route91);
|
||||
app.use("/api/project/addProject", route92);
|
||||
app.use("/api/project/addVisualManual", route93);
|
||||
app.use("/api/project/deleteDirectorManual", route94);
|
||||
app.use("/api/project/deleteVisualManual", route95);
|
||||
app.use("/api/project/delProject", route96);
|
||||
app.use("/api/project/editDirectorlManual", route97);
|
||||
app.use("/api/project/editProject", route98);
|
||||
app.use("/api/project/editVisualManual", route99);
|
||||
app.use("/api/project/getModelDetails", route100);
|
||||
app.use("/api/project/getProject", route101);
|
||||
app.use("/api/project/getVisualManual", route102);
|
||||
app.use("/api/project/queryDirectorManual", route103);
|
||||
app.use("/api/project/visualManual", route104);
|
||||
app.use("/api/script/addScript", route105);
|
||||
app.use("/api/script/batchAddScript", route106);
|
||||
app.use("/api/script/delScript", route107);
|
||||
app.use("/api/script/exportScript", route108);
|
||||
app.use("/api/script/extractAssets", route109);
|
||||
app.use("/api/script/getAiRegex", route110);
|
||||
app.use("/api/script/getScrptApi", route111);
|
||||
app.use("/api/script/pollScriptAssets", route112);
|
||||
app.use("/api/script/updateScript", route113);
|
||||
app.use("/api/scriptAgent/getPlanData", route114);
|
||||
app.use("/api/scriptAgent/setPlanData", route115);
|
||||
app.use("/api/scriptAgent/updateData", route116);
|
||||
app.use("/api/setting/about/checkUpdate", route117);
|
||||
app.use("/api/setting/about/downloadApp", route118);
|
||||
app.use("/api/setting/agentDeploy/agentSetKey", route119);
|
||||
app.use("/api/setting/agentDeploy/deployAgentModel", route120);
|
||||
app.use("/api/setting/agentDeploy/getAgentDeploy", route121);
|
||||
app.use("/api/setting/agentDeploy/getAgentUseMode", route122);
|
||||
app.use("/api/setting/agentDeploy/updateUseMode", route123);
|
||||
app.use("/api/setting/dbConfig/clearData", route124);
|
||||
app.use("/api/setting/dbConfig/clearTable", route125);
|
||||
app.use("/api/setting/dbConfig/dbInfo", route126);
|
||||
app.use("/api/setting/dbConfig/exportData", route127);
|
||||
app.use("/api/setting/dbConfig/importData", route128);
|
||||
app.use("/api/setting/dev/getSwitchAiDevTool", route129);
|
||||
app.use("/api/setting/dev/updateSwitchAiDevTool", route130);
|
||||
app.use("/api/setting/fileManagement/openFolder", route131);
|
||||
app.use("/api/setting/getTextModel", route132);
|
||||
app.use("/api/setting/loginConfig/getUser", route133);
|
||||
app.use("/api/setting/loginConfig/updateUserPwd", route134);
|
||||
app.use("/api/setting/memoryConfig/delAllMemory", route135);
|
||||
app.use("/api/setting/memoryConfig/getMemory", route136);
|
||||
app.use("/api/setting/memoryConfig/sureMemory", route137);
|
||||
app.use("/api/setting/modelMap/bindingPrompt", route138);
|
||||
app.use("/api/setting/modelMap/deletePrompt", route139);
|
||||
app.use("/api/setting/modelMap/getImageAndVideoModel", route140);
|
||||
app.use("/api/setting/modelMap/getPromptList", route141);
|
||||
app.use("/api/setting/modelMap/savePrompt", route142);
|
||||
app.use("/api/setting/modelMap/updatePrompt", route143);
|
||||
app.use("/api/setting/promptManage/getPrompt", route144);
|
||||
app.use("/api/setting/promptManage/updatePrompt", route145);
|
||||
app.use("/api/setting/skillManagement/getSkillContent", route146);
|
||||
app.use("/api/setting/skillManagement/getSkillList", route147);
|
||||
app.use("/api/setting/skillManagement/saveSkillContent", route148);
|
||||
app.use("/api/setting/vendorConfig/addVendor", route149);
|
||||
app.use("/api/setting/vendorConfig/addVendorModel", route150);
|
||||
app.use("/api/setting/vendorConfig/deleteVendor", route151);
|
||||
app.use("/api/setting/vendorConfig/delVendorModel", route152);
|
||||
app.use("/api/setting/vendorConfig/enableVendor", route153);
|
||||
app.use("/api/setting/vendorConfig/getCodeByLink", route154);
|
||||
app.use("/api/setting/vendorConfig/getVendorList", route155);
|
||||
app.use("/api/setting/vendorConfig/modelTest", route156);
|
||||
app.use("/api/setting/vendorConfig/modelTest/imageTest", route157);
|
||||
app.use("/api/setting/vendorConfig/modelTest/textTest", route158);
|
||||
app.use("/api/setting/vendorConfig/modelTest/videoTest", route159);
|
||||
app.use("/api/setting/vendorConfig/updateCode", route160);
|
||||
app.use("/api/setting/vendorConfig/updateVendorInputs", route161);
|
||||
app.use("/api/setting/vendorConfig/upVendorModel", route162);
|
||||
app.use("/api/task/getProject", route163);
|
||||
app.use("/api/task/getTaskApi", route164);
|
||||
app.use("/api/task/getTaskCategories", route165);
|
||||
app.use("/api/task/taskDetails", route166);
|
||||
app.use("/api/test/test", route167);
|
||||
app.use("/api/login/me", route36);
|
||||
app.use("/api/login/register", route37);
|
||||
app.use("/api/login/resetPassword", route38);
|
||||
app.use("/api/login/sendSmsCode", route39);
|
||||
app.use("/api/modelSelect/getModelDetail", route40);
|
||||
app.use("/api/modelSelect/getModelList", route41);
|
||||
app.use("/api/novel/addNovel", route42);
|
||||
app.use("/api/novel/batchDeleteNovel", route43);
|
||||
app.use("/api/novel/delNovel", route44);
|
||||
app.use("/api/novel/event/batchDeleteEvent", route45);
|
||||
app.use("/api/novel/event/deletEvent", route46);
|
||||
app.use("/api/novel/event/generateEvents", route47);
|
||||
app.use("/api/novel/event/getEvent", route48);
|
||||
app.use("/api/novel/getNovel", route49);
|
||||
app.use("/api/novel/getNovelData", route50);
|
||||
app.use("/api/novel/getNovelEventState", route51);
|
||||
app.use("/api/novel/getNovelIndex", route52);
|
||||
app.use("/api/novel/updateNovel", route53);
|
||||
app.use("/api/other/deleteAllData", route54);
|
||||
app.use("/api/other/getVersion", route55);
|
||||
app.use("/api/production/assets/batchGenerateAssetsImage", route56);
|
||||
app.use("/api/production/assets/deleteAssetsDireve", route57);
|
||||
app.use("/api/production/assets/pollingImage", route58);
|
||||
app.use("/api/production/assets/updateAssetsUrl", route59);
|
||||
app.use("/api/production/editImage/generateFlowImage", route60);
|
||||
app.use("/api/production/editImage/getImageDefaultModle", route61);
|
||||
app.use("/api/production/editImage/getImageFlow", route62);
|
||||
app.use("/api/production/editImage/saveImageFlow", route63);
|
||||
app.use("/api/production/editImage/updateImageFlow", route64);
|
||||
app.use("/api/production/editImage/uploadImage", route65);
|
||||
app.use("/api/production/getFlowData", route66);
|
||||
app.use("/api/production/getStoryboardData", route67);
|
||||
app.use("/api/production/saveFlowData", route68);
|
||||
app.use("/api/production/storyboard/addStoryboard", route69);
|
||||
app.use("/api/production/storyboard/batchAddStoryboardInfo", route70);
|
||||
app.use("/api/production/storyboard/batchDelete", route71);
|
||||
app.use("/api/production/storyboard/batchGenerateImage", route72);
|
||||
app.use("/api/production/storyboard/downPreviewImage", route73);
|
||||
app.use("/api/production/storyboard/editStoryboardInfo", route74);
|
||||
app.use("/api/production/storyboard/getStoryboardData", route75);
|
||||
app.use("/api/production/storyboard/pollingImage", route76);
|
||||
app.use("/api/production/storyboard/previewImage", route77);
|
||||
app.use("/api/production/storyboard/removeFrame", route78);
|
||||
app.use("/api/production/storyboard/updateStoryboardUrl", route79);
|
||||
app.use("/api/production/workbench/addTrack", route80);
|
||||
app.use("/api/production/workbench/batchGeneratePrompt", route81);
|
||||
app.use("/api/production/workbench/batchGenerateVideo", route82);
|
||||
app.use("/api/production/workbench/checkVideoStateList", route83);
|
||||
app.use("/api/production/workbench/deleteTrack", route84);
|
||||
app.use("/api/production/workbench/delVideo", route85);
|
||||
app.use("/api/production/workbench/generateVideo", route86);
|
||||
app.use("/api/production/workbench/generateVideoPrompt", route87);
|
||||
app.use("/api/production/workbench/getAudioBindAssetsList", route88);
|
||||
app.use("/api/production/workbench/getFileUrl", route89);
|
||||
app.use("/api/production/workbench/getGenerateData", route90);
|
||||
app.use("/api/production/workbench/getVideoList", route91);
|
||||
app.use("/api/production/workbench/selectVideo", route92);
|
||||
app.use("/api/production/workbench/updateVideoDuration", route93);
|
||||
app.use("/api/production/workbench/updateVideoPrompt", route94);
|
||||
app.use("/api/project/addDirectorManual", route95);
|
||||
app.use("/api/project/addProject", route96);
|
||||
app.use("/api/project/addVisualManual", route97);
|
||||
app.use("/api/project/deleteDirectorManual", route98);
|
||||
app.use("/api/project/deleteVisualManual", route99);
|
||||
app.use("/api/project/delProject", route100);
|
||||
app.use("/api/project/editDirectorlManual", route101);
|
||||
app.use("/api/project/editProject", route102);
|
||||
app.use("/api/project/editVisualManual", route103);
|
||||
app.use("/api/project/getModelDetails", route104);
|
||||
app.use("/api/project/getProject", route105);
|
||||
app.use("/api/project/getSingleProject", route106);
|
||||
app.use("/api/project/getVisualManual", route107);
|
||||
app.use("/api/project/queryDirectorManual", route108);
|
||||
app.use("/api/project/visualManual", route109);
|
||||
app.use("/api/script/addScript", route110);
|
||||
app.use("/api/script/batchAddScript", route111);
|
||||
app.use("/api/script/delScript", route112);
|
||||
app.use("/api/script/exportScript", route113);
|
||||
app.use("/api/script/extractAssets", route114);
|
||||
app.use("/api/script/getAiRegex", route115);
|
||||
app.use("/api/script/getScrptApi", route116);
|
||||
app.use("/api/script/pollScriptAssets", route117);
|
||||
app.use("/api/script/updateScript", route118);
|
||||
app.use("/api/scriptAgent/getPlanData", route119);
|
||||
app.use("/api/scriptAgent/setPlanData", route120);
|
||||
app.use("/api/scriptAgent/updateData", route121);
|
||||
app.use("/api/setting/about/checkUpdate", route122);
|
||||
app.use("/api/setting/about/downloadApp", route123);
|
||||
app.use("/api/setting/agentDeploy/agentSetKey", route124);
|
||||
app.use("/api/setting/agentDeploy/deployAgentModel", route125);
|
||||
app.use("/api/setting/agentDeploy/getAgentDeploy", route126);
|
||||
app.use("/api/setting/agentDeploy/getAgentUseMode", route127);
|
||||
app.use("/api/setting/agentDeploy/updateUseMode", route128);
|
||||
app.use("/api/setting/dbConfig/clearData", route129);
|
||||
app.use("/api/setting/dbConfig/clearTable", route130);
|
||||
app.use("/api/setting/dbConfig/dbInfo", route131);
|
||||
app.use("/api/setting/dbConfig/exportData", route132);
|
||||
app.use("/api/setting/dbConfig/importData", route133);
|
||||
app.use("/api/setting/dev/getSwitchAiDevTool", route134);
|
||||
app.use("/api/setting/dev/updateSwitchAiDevTool", route135);
|
||||
app.use("/api/setting/fileManagement/openFolder", route136);
|
||||
app.use("/api/setting/getTextModel", route137);
|
||||
app.use("/api/setting/loginConfig/getUser", route138);
|
||||
app.use("/api/setting/loginConfig/updateUserPwd", route139);
|
||||
app.use("/api/setting/memoryConfig/delAllMemory", route140);
|
||||
app.use("/api/setting/memoryConfig/getMemory", route141);
|
||||
app.use("/api/setting/memoryConfig/sureMemory", route142);
|
||||
app.use("/api/setting/modelMap/bindingPrompt", route143);
|
||||
app.use("/api/setting/modelMap/deletePrompt", route144);
|
||||
app.use("/api/setting/modelMap/getImageAndVideoModel", route145);
|
||||
app.use("/api/setting/modelMap/getPromptList", route146);
|
||||
app.use("/api/setting/modelMap/savePrompt", route147);
|
||||
app.use("/api/setting/modelMap/updatePrompt", route148);
|
||||
app.use("/api/setting/promptManage/getPrompt", route149);
|
||||
app.use("/api/setting/promptManage/updatePrompt", route150);
|
||||
app.use("/api/setting/skillManagement/getSkillContent", route151);
|
||||
app.use("/api/setting/skillManagement/getSkillList", route152);
|
||||
app.use("/api/setting/skillManagement/saveSkillContent", route153);
|
||||
app.use("/api/setting/vendorConfig/addVendor", route154);
|
||||
app.use("/api/setting/vendorConfig/addVendorModel", route155);
|
||||
app.use("/api/setting/vendorConfig/deleteVendor", route156);
|
||||
app.use("/api/setting/vendorConfig/delVendorModel", route157);
|
||||
app.use("/api/setting/vendorConfig/enableVendor", route158);
|
||||
app.use("/api/setting/vendorConfig/getCodeByLink", route159);
|
||||
app.use("/api/setting/vendorConfig/getVendorList", route160);
|
||||
app.use("/api/setting/vendorConfig/modelTest", route161);
|
||||
app.use("/api/setting/vendorConfig/modelTest/imageTest", route162);
|
||||
app.use("/api/setting/vendorConfig/modelTest/textTest", route163);
|
||||
app.use("/api/setting/vendorConfig/modelTest/videoTest", route164);
|
||||
app.use("/api/setting/vendorConfig/updateCode", route165);
|
||||
app.use("/api/setting/vendorConfig/updateVendorInputs", route166);
|
||||
app.use("/api/setting/vendorConfig/upVendorModel", route167);
|
||||
app.use("/api/task/getProject", route168);
|
||||
app.use("/api/task/getTaskApi", route169);
|
||||
app.use("/api/task/getTaskCategories", route170);
|
||||
app.use("/api/task/taskDetails", route171);
|
||||
app.use("/api/test/test", route172);
|
||||
}
|
||||
|
||||
@ -1,18 +1,12 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { success, error } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import { z } from "zod";
|
||||
import { createAuthToken, getTokenKey, publicUser } from "@/lib/auth";
|
||||
import { hashPassword, isHashedPassword, verifyPassword } from "@/lib/password";
|
||||
const router = express.Router();
|
||||
|
||||
export function setToken(payload: string | object, expiresIn: string | number, secret: string): string {
|
||||
if (!payload || typeof secret !== "string" || !secret) {
|
||||
throw new Error("参数不合法");
|
||||
}
|
||||
return (jwt.sign as any)(payload, secret, { expiresIn });
|
||||
}
|
||||
|
||||
// 登录
|
||||
export default router.post(
|
||||
"/",
|
||||
@ -21,24 +15,24 @@ export default router.post(
|
||||
password: z.string(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
const username = String(req.body.username || "").trim();
|
||||
const password = String(req.body.password || "");
|
||||
|
||||
const data = await u.db("o_user").where("name", "=", username).first();
|
||||
const data = await u.db("o_user").where("name", "=", username).orWhere("phone", username).first();
|
||||
if (!data) return res.status(400).send(error("登录失败"));
|
||||
|
||||
if (data!.password == password && data!.name == username) {
|
||||
const tokenData = await u.db("o_setting").where("key", "tokenKey").first();
|
||||
if (!tokenData) return res.status(400).send(error("未找到tokenKey"));
|
||||
const token = setToken(
|
||||
{
|
||||
id: data!.id,
|
||||
name: data!.name,
|
||||
},
|
||||
"180Days",
|
||||
tokenData?.value as string,
|
||||
);
|
||||
const validPassword = await verifyPassword(password, data.password);
|
||||
if (validPassword) {
|
||||
const tokenKey = await getTokenKey();
|
||||
if (!tokenKey) return res.status(400).send(error("未找到tokenKey"));
|
||||
|
||||
return res.status(200).send(success({ token: "Bearer " + token, name: data!.name, id: data!.id }, "登录成功"));
|
||||
if (!isHashedPassword(data.password)) {
|
||||
await u.db("o_user").where("id", data.id).update({ password: await hashPassword(password) });
|
||||
}
|
||||
|
||||
const user = publicUser(data);
|
||||
const token = createAuthToken(user, tokenKey);
|
||||
return res.status(200).send(success({ token: "Bearer " + token, ...user }, "登录成功"));
|
||||
} else {
|
||||
return res.status(400).send(error("用户名或密码错误"));
|
||||
}
|
||||
|
||||
13
src/routes/login/me.ts
Normal file
13
src/routes/login/me.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { getCurrentUser, publicUser } from "@/lib/auth";
|
||||
import { success, error } from "@/lib/responseFormat";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
export default router.get("/", async (req, res) => {
|
||||
const authUser = getCurrentUser(req);
|
||||
const user = await u.db("o_user").where("id", authUser.id).select("id", "name").first();
|
||||
if (!user) return res.status(401).send(error("用户不存在"));
|
||||
return res.status(200).send(success(publicUser(user)));
|
||||
});
|
||||
57
src/routes/login/register.ts
Normal file
57
src/routes/login/register.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { error, success } from "@/lib/responseFormat";
|
||||
import { createAuthToken, getTokenKey, publicUser } from "@/lib/auth";
|
||||
import { hashPassword } from "@/lib/password";
|
||||
import { verifySmsCode } from "@/lib/smsCode";
|
||||
import { isValidMainlandPhone } from "@/lib/sms";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
username: z.string().min(2).max(32),
|
||||
phone: z.string(),
|
||||
code: z.string(),
|
||||
password: z.string().min(6).max(64),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const username = String(req.body.username || "").trim();
|
||||
const phone = String(req.body.phone || "").trim();
|
||||
const code = String(req.body.code || "").trim();
|
||||
const password = String(req.body.password || "");
|
||||
|
||||
if (username.length < 2 || username.length > 32) {
|
||||
return res.status(400).send(error("用户名长度需为 2-32 位"));
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/.test(username)) {
|
||||
return res.status(400).send(error("用户名只能包含中文、英文、数字、下划线和横线"));
|
||||
}
|
||||
if (!isValidMainlandPhone(phone)) return res.status(400).send(error("手机号格式不正确"));
|
||||
|
||||
const exists = await u.db("o_user").where("name", username).first();
|
||||
if (exists) return res.status(400).send(error("用户名已存在"));
|
||||
const phoneExists = await u.db("o_user").where("phone", phone).first();
|
||||
if (phoneExists) return res.status(400).send(error("手机号已注册"));
|
||||
|
||||
const tokenKey = await getTokenKey();
|
||||
if (!tokenKey) return res.status(400).send(error("未找到tokenKey"));
|
||||
await verifySmsCode(phone, "register", code);
|
||||
|
||||
const id = Date.now();
|
||||
await u.db("o_user").insert({
|
||||
id,
|
||||
name: username,
|
||||
phone,
|
||||
password: await hashPassword(password),
|
||||
});
|
||||
|
||||
const user = publicUser({ id, name: username, phone });
|
||||
const token = createAuthToken(user, tokenKey);
|
||||
return res.status(200).send(success({ token: "Bearer " + token, ...user }, "注册成功"));
|
||||
},
|
||||
);
|
||||
36
src/routes/login/resetPassword.ts
Normal file
36
src/routes/login/resetPassword.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { error, success } from "@/lib/responseFormat";
|
||||
import { hashPassword } from "@/lib/password";
|
||||
import { verifySmsCode } from "@/lib/smsCode";
|
||||
import { isValidMainlandPhone } from "@/lib/sms";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
phone: z.string(),
|
||||
code: z.string(),
|
||||
password: z.string().min(6).max(64),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const phone = String(req.body.phone || "").trim();
|
||||
const code = String(req.body.code || "").trim();
|
||||
const password = String(req.body.password || "");
|
||||
|
||||
if (!isValidMainlandPhone(phone)) return res.status(400).send(error("手机号格式不正确"));
|
||||
|
||||
const user = await u.db("o_user").where("phone", phone).first();
|
||||
if (!user) return res.status(400).send(error("该手机号尚未注册"));
|
||||
|
||||
await verifySmsCode(phone, "resetPassword", code);
|
||||
await u.db("o_user").where("id", user.id).update({
|
||||
password: await hashPassword(password),
|
||||
});
|
||||
|
||||
return res.status(200).send(success(null, "密码已重置"));
|
||||
},
|
||||
);
|
||||
34
src/routes/login/sendSmsCode.ts
Normal file
34
src/routes/login/sendSmsCode.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { error, success } from "@/lib/responseFormat";
|
||||
import { createNumericCode, assertCanSendSmsCode, saveSmsCode } from "@/lib/smsCode";
|
||||
import { isValidMainlandPhone, maskPhone, sendSmsCode } from "@/lib/sms";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
phone: z.string(),
|
||||
purpose: z.enum(["register", "resetPassword"]),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const phone = String(req.body.phone || "").trim();
|
||||
const purpose = req.body.purpose as "register" | "resetPassword";
|
||||
|
||||
if (!isValidMainlandPhone(phone)) return res.status(400).send(error("手机号格式不正确"));
|
||||
|
||||
const user = await u.db("o_user").where("phone", phone).first();
|
||||
if (purpose === "register" && user) return res.status(400).send(error("手机号已注册"));
|
||||
if (purpose === "resetPassword" && !user) return res.status(400).send(error("该手机号尚未注册"));
|
||||
|
||||
await assertCanSendSmsCode(phone, purpose);
|
||||
const code = createNumericCode();
|
||||
await sendSmsCode(phone, code);
|
||||
await saveSmsCode(phone, purpose, code);
|
||||
|
||||
return res.status(200).send(success({ phone: maskPhone(phone), expiresIn: 300 }, "验证码已发送"));
|
||||
},
|
||||
);
|
||||
@ -3,6 +3,7 @@ import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import { getCurrentUser } from "@/lib/auth";
|
||||
const router = express.Router();
|
||||
|
||||
// 新增项目
|
||||
@ -23,6 +24,7 @@ export default router.post(
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { projectType, name, intro, type, directorManual, artStyle, videoRatio, imageModel, videoModel, imageQuality, mode } = req.body;
|
||||
const user = getCurrentUser(req);
|
||||
|
||||
await u.db("o_project").insert({
|
||||
id: Date.now(),
|
||||
@ -33,7 +35,7 @@ export default router.post(
|
||||
artStyle,
|
||||
videoRatio,
|
||||
directorManual,
|
||||
userId: 1,
|
||||
userId: user.id,
|
||||
imageModel,
|
||||
videoModel,
|
||||
createTime: Date.now(),
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { getCurrentUser } from "@/lib/auth";
|
||||
const router = express.Router();
|
||||
|
||||
// 获取项目
|
||||
export default router.post("/", async (req, res) => {
|
||||
const data = await u.db("o_project").select("*");
|
||||
const user = getCurrentUser(req);
|
||||
const data = await u.db("o_project").where("userId", user.id).select("*").orderBy("createTime", "desc");
|
||||
res.status(200).send(success(data));
|
||||
});
|
||||
|
||||
19
src/routes/project/getSingleProject.ts
Normal file
19
src/routes/project/getSingleProject.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
id: z.number(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { id } = req.body;
|
||||
const data = await u.db("o_project").where("id", id).select("*");
|
||||
res.status(200).send(success(data));
|
||||
},
|
||||
);
|
||||
@ -1,9 +1,11 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { getCurrentUser, publicUser } from "@/lib/auth";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.get("/", async (req, res) => {
|
||||
const data = await u.db("o_user").select("*").first();
|
||||
res.status(200).send(success(data));
|
||||
const user = getCurrentUser(req);
|
||||
const data = await u.db("o_user").where("id", user.id).select("id", "name", "phone").first();
|
||||
res.status(200).send(success(data ? publicUser(data) : null));
|
||||
});
|
||||
|
||||
@ -1,23 +1,35 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { error, success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import { getCurrentUser } from "@/lib/auth";
|
||||
import { hashPassword } from "@/lib/password";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
name: z.string(),
|
||||
password: z.string(),
|
||||
id: z.number(),
|
||||
password: z.string().optional().nullable(),
|
||||
id: z.number().optional().nullable(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { name, password, id } = req.body;
|
||||
await u.db("o_user").where("id", id).update({
|
||||
name,
|
||||
password,
|
||||
});
|
||||
const user = getCurrentUser(req);
|
||||
const name = String(req.body.name || "").trim();
|
||||
const password = String(req.body.password || "");
|
||||
|
||||
if (password && (password.length < 6 || password.length > 64)) {
|
||||
return res.status(400).send(error("密码长度需为 6-64 位"));
|
||||
}
|
||||
|
||||
const exists = await u.db("o_user").where("name", name).whereNot("id", user.id).first();
|
||||
if (exists) return res.status(400).send(error("用户名已存在"));
|
||||
|
||||
const updateData: { name: string; password?: string } = { name };
|
||||
if (password) updateData.password = await hashPassword(password);
|
||||
|
||||
await u.db("o_user").where("id", user.id).update(updateData);
|
||||
res.status(200).send(success("保存设置成功"));
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { getCurrentUser } from "@/lib/auth";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post("/", async (req, res) => {
|
||||
const list = await u.db("o_project").select("id", "name").groupBy("name");
|
||||
const user = getCurrentUser(req);
|
||||
const list = await u.db("o_project").where("userId", user.id).select("id", "name").groupBy("id", "name");
|
||||
const data = list.filter((item) => item.name);
|
||||
res.status(200).send(success(data));
|
||||
});
|
||||
|
||||
@ -2,7 +2,8 @@ import express from "express";
|
||||
import u from "@/utils";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import { number, z } from "zod";
|
||||
import { z } from "zod";
|
||||
import { getCurrentUser } from "@/lib/auth";
|
||||
const router = express.Router();
|
||||
export default router.post(
|
||||
"/",
|
||||
@ -15,10 +16,12 @@ export default router.post(
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { taskClass, state, projectId, page = 1, limit = 10 }: any = req.body;
|
||||
const user = getCurrentUser(req);
|
||||
const offset = (page - 1) * limit;
|
||||
const data = await u
|
||||
.db("o_tasks")
|
||||
.leftJoin("o_project", "o_project.id", "o_tasks.projectId")
|
||||
.where("o_project.userId", user.id)
|
||||
.andWhere((qb) => {
|
||||
if (taskClass) {
|
||||
qb.andWhere("o_tasks.taskClass", taskClass);
|
||||
@ -36,6 +39,8 @@ export default router.post(
|
||||
.orderBy("o_tasks.id", "desc");
|
||||
const totalQuery = (await u
|
||||
.db("o_tasks")
|
||||
.leftJoin("o_project", "o_project.id", "o_tasks.projectId")
|
||||
.where("o_project.userId", user.id)
|
||||
.andWhere((qb) => {
|
||||
if (taskClass) {
|
||||
qb.andWhere("o_tasks.taskClass", taskClass);
|
||||
|
||||
@ -1,27 +1,15 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import u from "@/utils";
|
||||
import { Namespace, Socket } from "socket.io";
|
||||
import * as agent from "@/agents/productionAgent/index";
|
||||
import ResTool from "@/socket/resTool";
|
||||
|
||||
async function verifyToken(rawToken: string): Promise<Boolean> {
|
||||
const setting = await u.db("o_setting").where("key", "tokenKey").select("value").first();
|
||||
if (!setting) return false;
|
||||
const { value: tokenKey } = setting;
|
||||
if (!rawToken) return false;
|
||||
const token = rawToken.replace("Bearer ", "");
|
||||
try {
|
||||
jwt.verify(token, tokenKey as string);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
import { assertProjectAccess, verifyAuthToken } from "@/lib/auth";
|
||||
|
||||
export default (nsp: Namespace) => {
|
||||
nsp.on("connection", async (socket: Socket) => {
|
||||
const token = socket.handshake.auth.token;
|
||||
if (!token || !(await verifyToken(token))) {
|
||||
const authUser = await verifyAuthToken(token);
|
||||
const projectId = Number(socket.handshake.auth.projectId);
|
||||
if (!authUser || !projectId || !(await assertProjectAccess(projectId, authUser.id))) {
|
||||
console.log("[productionAgent] 连接失败,token无效");
|
||||
socket.disconnect();
|
||||
return;
|
||||
@ -36,7 +24,7 @@ export default (nsp: Namespace) => {
|
||||
console.log("[productionAgent] 已连接:", socket.id);
|
||||
|
||||
let resTool = new ResTool(socket, {
|
||||
projectId: socket.handshake.auth.projectId,
|
||||
projectId,
|
||||
scriptId: socket.handshake.auth.scriptId,
|
||||
});
|
||||
let abortController: AbortController | null = null;
|
||||
@ -46,7 +34,11 @@ export default (nsp: Namespace) => {
|
||||
thinlLevel: 0,
|
||||
};
|
||||
|
||||
socket.on("updateContext", (data: { isolationKey: string; projectId: number; scriptId: number }, callback) => {
|
||||
socket.on("updateContext", async (data: { isolationKey: string; projectId: number; scriptId: number }, callback) => {
|
||||
if (!(await assertProjectAccess(Number(data.projectId), authUser.id))) {
|
||||
callback?.({ success: false, message: "无权访问该项目" });
|
||||
return;
|
||||
}
|
||||
isolationKey = data.isolationKey;
|
||||
resTool = new ResTool(socket, {
|
||||
projectId: data.projectId,
|
||||
|
||||
@ -1,27 +1,15 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import u from "@/utils";
|
||||
import { Namespace, Socket } from "socket.io";
|
||||
import * as agent from "@/agents/scriptAgent/index";
|
||||
import ResTool from "@/socket/resTool";
|
||||
|
||||
async function verifyToken(rawToken: string): Promise<Boolean> {
|
||||
const setting = await u.db("o_setting").where("key", "tokenKey").select("value").first();
|
||||
if (!setting) return false;
|
||||
const { value: tokenKey } = setting;
|
||||
if (!rawToken) return false;
|
||||
const token = rawToken.replace("Bearer ", "");
|
||||
try {
|
||||
jwt.verify(token, tokenKey as string);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
import { assertProjectAccess, verifyAuthToken } from "@/lib/auth";
|
||||
|
||||
export default (nsp: Namespace) => {
|
||||
nsp.on("connection", async (socket: Socket) => {
|
||||
const token = socket.handshake.auth.token;
|
||||
if (!token || !(await verifyToken(token))) {
|
||||
const authUser = await verifyAuthToken(token);
|
||||
const projectId = Number(socket.handshake.auth.projectId);
|
||||
if (!authUser || !projectId || !(await assertProjectAccess(projectId, authUser.id))) {
|
||||
console.log("[scriptAgent] 连接失败,token无效");
|
||||
socket.disconnect();
|
||||
return;
|
||||
@ -36,7 +24,7 @@ export default (nsp: Namespace) => {
|
||||
console.log("[scriptAgent] 已连接:", socket.id);
|
||||
|
||||
const resTool = new ResTool(socket, {
|
||||
projectId: socket.handshake.auth.projectId,
|
||||
projectId,
|
||||
});
|
||||
let abortController: AbortController | null = null;
|
||||
|
||||
|
||||
13
src/types/database.d.ts
vendored
13
src/types/database.d.ts
vendored
@ -140,6 +140,17 @@ export interface o_project {
|
||||
'videoModel'?: string | null;
|
||||
'videoRatio'?: string | null;
|
||||
}
|
||||
export interface o_smsCode {
|
||||
'attempts'?: number | null;
|
||||
'codeHash': string;
|
||||
'createTime': number;
|
||||
'expiresAt': number;
|
||||
'id'?: number;
|
||||
'phone': string;
|
||||
'purpose': string;
|
||||
'sentAt': number;
|
||||
'used'?: number | null;
|
||||
}
|
||||
export interface o_prompt {
|
||||
'data'?: string | null;
|
||||
'id'?: number;
|
||||
@ -211,6 +222,7 @@ export interface o_tasks {
|
||||
export interface o_user {
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
'phone'?: string | null;
|
||||
'password'?: string | null;
|
||||
}
|
||||
export interface o_vendorConfig {
|
||||
@ -262,6 +274,7 @@ export interface DB {
|
||||
"o_script": o_script;
|
||||
"o_scriptAssets": o_scriptAssets;
|
||||
"o_setting": o_setting;
|
||||
"o_smsCode": o_smsCode;
|
||||
"o_skillAttribution": o_skillAttribution;
|
||||
"o_skillList": o_skillList;
|
||||
"o_storyboard": o_storyboard;
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"web-core",
|
||||
"data/**/*.ts",
|
||||
"dist",
|
||||
"build"
|
||||
|
||||
@ -4,8 +4,11 @@
|
||||
<t-form-item :label="$t('settings.login.username')" name="name">
|
||||
<t-input v-model="formData.name" :placeholder="$t('settings.login.usernamePlaceholder')" clearable width="100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="手机号" name="phone">
|
||||
<t-input v-model="formData.phone" placeholder="未绑定手机号" disabled width="100%" />
|
||||
</t-form-item>
|
||||
<t-form-item :label="$t('settings.login.password')" name="password">
|
||||
<t-input v-model="formData.password" type="password" :placeholder="$t('settings.login.passwordPlaceholder')" />
|
||||
<t-input v-model="formData.password" type="password" placeholder="留空则不修改密码" />
|
||||
</t-form-item>
|
||||
<t-form-item :status-icon="false">
|
||||
<t-space size="small">
|
||||
@ -23,6 +26,7 @@ import axios from "@/utils/axios";
|
||||
interface UserForm {
|
||||
id: number | null;
|
||||
name: string;
|
||||
phone: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
@ -32,6 +36,7 @@ const loading = ref(false);
|
||||
const formData = ref<UserForm>({
|
||||
id: null,
|
||||
name: "",
|
||||
phone: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
@ -41,8 +46,7 @@ const formRules: FormRules<UserForm> = {
|
||||
{ min: 2, max: 20, message: $t("settings.login.msg.usernameLength"), trigger: "blur" },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: $t("settings.login.msg.enterPassword"), trigger: "blur" },
|
||||
{ min: 6, max: 20, message: $t("settings.login.msg.passwordLength"), trigger: "blur" },
|
||||
{ min: 6, max: 64, message: $t("settings.login.msg.passwordLength"), trigger: "blur" },
|
||||
],
|
||||
};
|
||||
|
||||
@ -52,7 +56,8 @@ async function fetchUserInfo() {
|
||||
formData.value = {
|
||||
id: res.data.id ?? null,
|
||||
name: res.data.name ?? "",
|
||||
password: res.data.password ?? "",
|
||||
phone: res.data.phone ?? "",
|
||||
password: "",
|
||||
};
|
||||
} catch (error) {
|
||||
window.$message.error($t("settings.login.msg.fetchFailed"));
|
||||
|
||||
@ -44,6 +44,7 @@ async function handleLogout() {
|
||||
localStorage.removeItem("token");
|
||||
// 清除其他可能的用户数据
|
||||
localStorage.removeItem("user");
|
||||
localStorage.removeItem("userId");
|
||||
|
||||
window.$message.success($t("settings.logout.msg.logoutSuccess"));
|
||||
|
||||
|
||||
@ -68,39 +68,86 @@
|
||||
<section class="formBox" aria-labelledby="login-title">
|
||||
<div class="formHeader">
|
||||
<span class="formEyebrow"><i></i> SECURE ACCESS</span>
|
||||
<h2 id="login-title">登录</h2>
|
||||
<p>进入团队短剧生产工作台</p>
|
||||
<h2 id="login-title">{{ formTitle }}</h2>
|
||||
<p>{{ formSubtitle }}</p>
|
||||
</div>
|
||||
|
||||
<form class="login-form" @submit.prevent="handleLogin">
|
||||
<label class="fieldLabel" for="login-username">用户名</label>
|
||||
<t-input id="login-username" v-model="state.user.username" placeholder="请输入用户名" autocomplete="username" size="large">
|
||||
<div v-if="!isResetMode" class="authSwitch" role="tablist" aria-label="账号入口">
|
||||
<button type="button" :class="{ active: authMode === 'login' }" @click="setAuthMode('login')">登录</button>
|
||||
<button type="button" :class="{ active: authMode === 'register' }" @click="setAuthMode('register')">注册</button>
|
||||
</div>
|
||||
|
||||
<form class="login-form" @submit.prevent="handleSubmit">
|
||||
<template v-if="!isResetMode">
|
||||
<label class="fieldLabel" for="login-username">用户名/手机号</label>
|
||||
<t-input id="login-username" v-model="state.user.username" placeholder="请输入用户名或手机号" autocomplete="username" size="large">
|
||||
<template #prefix-icon>
|
||||
<t-icon name="user" />
|
||||
</template>
|
||||
</t-input>
|
||||
<label class="fieldLabel" for="login-password">密码</label>
|
||||
</template>
|
||||
|
||||
<template v-if="needsSmsCode">
|
||||
<label class="fieldLabel" for="login-phone">手机号</label>
|
||||
<t-input id="login-phone" v-model="state.user.phone" placeholder="请输入手机号" autocomplete="tel" size="large">
|
||||
<template #prefix-icon>
|
||||
<t-icon name="mobile" />
|
||||
</template>
|
||||
</t-input>
|
||||
|
||||
<label class="fieldLabel" for="login-code">验证码</label>
|
||||
<div class="codeRow">
|
||||
<t-input id="login-code" v-model="state.user.code" placeholder="请输入短信验证码" autocomplete="one-time-code" size="large">
|
||||
<template #prefix-icon>
|
||||
<t-icon name="lock-on" />
|
||||
</template>
|
||||
</t-input>
|
||||
<t-button theme="default" size="large" :loading="state.smsLoading" :disabled="smsCountdown > 0" @click="handleSendSmsCode">
|
||||
{{ smsCountdown > 0 ? `${smsCountdown}s` : "获取验证码" }}
|
||||
</t-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<label class="fieldLabel" for="login-password">{{ isResetMode ? "新密码" : "密码" }}</label>
|
||||
<t-input
|
||||
id="login-password"
|
||||
v-model="state.user.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
autocomplete="current-password"
|
||||
:placeholder="isResetMode ? '请输入新密码' : '请输入密码'"
|
||||
:autocomplete="isRegisterMode || isResetMode ? 'new-password' : 'current-password'"
|
||||
size="large">
|
||||
<template #prefix-icon>
|
||||
<t-icon name="lock-on" />
|
||||
</template>
|
||||
</t-input>
|
||||
|
||||
<div class="loginOptions">
|
||||
<t-checkbox v-model="rememberMe">记住我</t-checkbox>
|
||||
<button class="forgotBtn" type="button" @click="handleForgotPassword">忘记密码?</button>
|
||||
<template v-if="isRegisterMode || isResetMode">
|
||||
<label class="fieldLabel" for="login-confirm-password">确认密码</label>
|
||||
<t-input
|
||||
id="login-confirm-password"
|
||||
v-model="state.user.confirmPassword"
|
||||
type="password"
|
||||
placeholder="请再次输入密码"
|
||||
autocomplete="new-password"
|
||||
size="large">
|
||||
<template #prefix-icon>
|
||||
<t-icon name="lock-on" />
|
||||
</template>
|
||||
</t-input>
|
||||
</template>
|
||||
|
||||
<div v-if="!isRegisterMode" class="loginOptions">
|
||||
<t-checkbox v-if="!isResetMode" v-model="rememberMe">记住我</t-checkbox>
|
||||
<span v-else></span>
|
||||
<button class="forgotBtn" type="button" @click="handleForgotPassword">{{ isResetMode ? "返回登录" : "忘记密码?" }}</button>
|
||||
</div>
|
||||
|
||||
<t-button class="loginBtn" theme="primary" size="large" type="submit" :loading="state.loginLoading" block>进入工作台</t-button>
|
||||
<t-button class="loginBtn" theme="primary" size="large" type="submit" :loading="state.loginLoading" block>
|
||||
{{ primaryButtonText }}
|
||||
</t-button>
|
||||
</form>
|
||||
|
||||
<div class="tips">
|
||||
<div v-if="!needsSmsCode" class="tips">
|
||||
<span>默认账号</span>
|
||||
<strong>admin</strong>
|
||||
<span>/</span>
|
||||
@ -141,6 +188,27 @@ const defaultBaseUrl = getDefaultApiBaseUrl();
|
||||
const tempBaseUrl = ref(baseUrl.value);
|
||||
const rememberedUsername = typeof window !== "undefined" ? localStorage.getItem("toonflowRememberedUsername") || "" : "";
|
||||
const rememberMe = ref(Boolean(rememberedUsername));
|
||||
const authMode = ref("login");
|
||||
const isRegisterMode = computed(() => authMode.value === "register");
|
||||
const isResetMode = computed(() => authMode.value === "reset");
|
||||
const needsSmsCode = computed(() => isRegisterMode.value || isResetMode.value);
|
||||
const smsCountdown = ref(0);
|
||||
let smsTimer = null;
|
||||
const formTitle = computed(() => {
|
||||
if (isRegisterMode.value) return "创建账号";
|
||||
if (isResetMode.value) return "重置密码";
|
||||
return "登录";
|
||||
});
|
||||
const formSubtitle = computed(() => {
|
||||
if (isRegisterMode.value) return "手机号验证后创建独立工作区";
|
||||
if (isResetMode.value) return "通过手机号验证码找回访问权限";
|
||||
return "进入你的短剧生产工作台";
|
||||
});
|
||||
const primaryButtonText = computed(() => {
|
||||
if (isRegisterMode.value) return "创建并进入";
|
||||
if (isResetMode.value) return "重置密码";
|
||||
return "进入工作台";
|
||||
});
|
||||
|
||||
// 保存设置
|
||||
const handleSaveSetting = () => {
|
||||
@ -154,8 +222,12 @@ const state = ref({
|
||||
loginLoading: false,
|
||||
user: {
|
||||
username: rememberedUsername,
|
||||
phone: "",
|
||||
code: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
},
|
||||
smsLoading: false,
|
||||
rules: {
|
||||
username: [{ required: true, message: "请输入用户名" }],
|
||||
password: [{ required: true, message: "请输入密码" }],
|
||||
@ -163,7 +235,67 @@ const state = ref({
|
||||
});
|
||||
|
||||
const handleForgotPassword = () => {
|
||||
window.$message.info("请联系管理员重置密码");
|
||||
setAuthMode(isResetMode.value ? "login" : "reset");
|
||||
};
|
||||
|
||||
const setAuthMode = (mode) => {
|
||||
authMode.value = mode;
|
||||
state.value.user.code = "";
|
||||
state.value.user.password = "";
|
||||
state.value.user.confirmPassword = "";
|
||||
};
|
||||
|
||||
const validatePhone = () => {
|
||||
const phone = state.value.user.phone.trim();
|
||||
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
||||
window.$message.warning("请输入正确的手机号");
|
||||
return "";
|
||||
}
|
||||
return phone;
|
||||
};
|
||||
|
||||
const startSmsCountdown = () => {
|
||||
smsCountdown.value = 60;
|
||||
if (smsTimer) window.clearInterval(smsTimer);
|
||||
smsTimer = window.setInterval(() => {
|
||||
smsCountdown.value -= 1;
|
||||
if (smsCountdown.value <= 0 && smsTimer) {
|
||||
window.clearInterval(smsTimer);
|
||||
smsTimer = null;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleSendSmsCode = () => {
|
||||
const phone = validatePhone();
|
||||
if (!phone) return;
|
||||
state.value.smsLoading = true;
|
||||
axios
|
||||
.post("/login/sendSmsCode", {
|
||||
phone,
|
||||
purpose: isResetMode.value ? "resetPassword" : "register",
|
||||
})
|
||||
.then(() => {
|
||||
startSmsCountdown();
|
||||
window.$message.success("验证码已发送");
|
||||
})
|
||||
.catch((e) => {
|
||||
window.$message.error(e.message);
|
||||
})
|
||||
.finally(() => {
|
||||
state.value.smsLoading = false;
|
||||
});
|
||||
};
|
||||
|
||||
const persistSession = (data, username) => {
|
||||
localStorage.setItem("token", data.token);
|
||||
localStorage.setItem("userId", data.id);
|
||||
if (rememberMe.value && authMode.value === "login") {
|
||||
localStorage.setItem("toonflowRememberedUsername", username);
|
||||
} else if (authMode.value === "login") {
|
||||
localStorage.removeItem("toonflowRememberedUsername");
|
||||
}
|
||||
Router.push("/project");
|
||||
};
|
||||
|
||||
const handleLogin = () => {
|
||||
@ -176,14 +308,7 @@ const handleLogin = () => {
|
||||
axios
|
||||
.post("/login/login", obj)
|
||||
.then(({ data }) => {
|
||||
localStorage.setItem("token", data.token);
|
||||
localStorage.setItem("userId", data.id);
|
||||
if (rememberMe.value) {
|
||||
localStorage.setItem("toonflowRememberedUsername", obj.username);
|
||||
} else {
|
||||
localStorage.removeItem("toonflowRememberedUsername");
|
||||
}
|
||||
Router.push("/project");
|
||||
persistSession(data, obj.username);
|
||||
window.$message.success("登录成功");
|
||||
state.value.loginLoading = false;
|
||||
})
|
||||
@ -192,6 +317,68 @@ const handleLogin = () => {
|
||||
window.$message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const handleRegister = () => {
|
||||
const { username, phone, code, password, confirmPassword } = state.value.user;
|
||||
if (!username || !phone || !code || !password || !confirmPassword) {
|
||||
window.$message.warning("请完整填写注册信息");
|
||||
return;
|
||||
}
|
||||
if (!validatePhone()) return;
|
||||
if (password !== confirmPassword) {
|
||||
window.$message.warning("两次输入的密码不一致");
|
||||
return;
|
||||
}
|
||||
state.value.loginLoading = true;
|
||||
axios
|
||||
.post("/login/register", { username, phone, code, password })
|
||||
.then(({ data }) => {
|
||||
persistSession(data, username);
|
||||
window.$message.success("注册成功");
|
||||
state.value.loginLoading = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
state.value.loginLoading = false;
|
||||
window.$message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const handleResetPassword = () => {
|
||||
const { phone, code, password, confirmPassword } = state.value.user;
|
||||
if (!phone || !code || !password || !confirmPassword) {
|
||||
window.$message.warning("请完整填写重置信息");
|
||||
return;
|
||||
}
|
||||
if (!validatePhone()) return;
|
||||
if (password !== confirmPassword) {
|
||||
window.$message.warning("两次输入的密码不一致");
|
||||
return;
|
||||
}
|
||||
state.value.loginLoading = true;
|
||||
axios
|
||||
.post("/login/resetPassword", { phone, code, password })
|
||||
.then(() => {
|
||||
state.value.user.username = phone;
|
||||
setAuthMode("login");
|
||||
window.$message.success("密码已重置,请重新登录");
|
||||
})
|
||||
.catch((e) => {
|
||||
window.$message.error(e.message);
|
||||
})
|
||||
.finally(() => {
|
||||
state.value.loginLoading = false;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (isResetMode.value) handleResetPassword();
|
||||
else if (isRegisterMode.value) handleRegister();
|
||||
else handleLogin();
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (smsTimer) window.clearInterval(smsTimer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -538,6 +725,39 @@ const handleLogin = () => {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.authSwitch {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4px;
|
||||
height: 42px;
|
||||
margin: -6px 0 20px;
|
||||
padding: 4px;
|
||||
border: 1px solid rgba(185, 188, 210, 0.16);
|
||||
border-radius: var(--air-field-radius);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
border-radius: calc(var(--air-field-radius) - 4px);
|
||||
background: transparent;
|
||||
color: rgba(226, 230, 246, 0.68);
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 780;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 160ms var(--air-ease-out),
|
||||
color 160ms var(--air-ease-out),
|
||||
box-shadow 160ms var(--air-ease-out);
|
||||
}
|
||||
|
||||
button.active {
|
||||
color: #07121a;
|
||||
background: linear-gradient(100deg, #65d9cb 0%, #8ea3ff 100%);
|
||||
box-shadow: 0 10px 22px rgba(101, 217, 203, 0.18);
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@ -553,6 +773,24 @@ const handleLogin = () => {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.codeRow {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 116px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
:deep(.t-button) {
|
||||
height: 48px !important;
|
||||
min-height: 48px !important;
|
||||
border-color: rgba(185, 188, 210, 0.2) !important;
|
||||
border-radius: var(--air-field-radius) !important;
|
||||
background: rgba(255, 255, 255, 0.07) !important;
|
||||
color: var(--air-text-primary) !important;
|
||||
font-size: 13px;
|
||||
font-weight: 750;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
:deep(.t-input) {
|
||||
height: 48px !important;
|
||||
@ -856,6 +1094,10 @@ const handleLogin = () => {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.codeRow {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tips {
|
||||
flex-wrap: wrap;
|
||||
padding-top: 24px;
|
||||
|
||||
@ -27,8 +27,9 @@ instance.interceptors.response.use(
|
||||
return response.data;
|
||||
},
|
||||
function (error) {
|
||||
if (error.status === 401) {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("userId");
|
||||
router.push("/login");
|
||||
MessagePlugin.error(window.$t("common.sessionExpired"));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user