| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- /**
- * 云函数:authCreditManager(账号、总积分、充值订单、管理员调账)
- *
- * 部署后把函数 objectId 填入 src/app/services/cloud-functions.ts 的 authCredit。
- * 管理员策略:
- * 1. 第一位注册用户自动为 admin;
- * 2. 也可在 ADMIN_IDENTIFIERS 中配置用户名/邮箱/手机号。
- */
- const ADMIN_IDENTIFIERS = ['admin'];
- const REGISTER_GIFT_CREDITS = 10;
- const CREDIT_PER_CNY = 10;
- const PARSE_API_HOST = 'https://server.fmode.cn';
- const PARSE_APP_ID = 'ncloudmaster';
- async function handler(request, response) {
- try {
- await ensureTables();
- const action = pickParam(request, 'action') || '';
- if (action === 'register') return register(request, response);
- if (action === 'login') return login(request, response);
- if (action === 'me') return me(request, response);
- if (action === 'changePassword') return changePassword(request, response);
- if (action === 'balance') return balance(request, response);
- if (action === 'ledger') return ledger(request, response);
- if (action === 'reserve') return reserve(request, response);
- if (action === 'commitReservation') return commitReservation(request, response);
- if (action === 'refundReservation') return refundReservation(request, response);
- if (action === 'createRechargeOrder') return createRechargeOrder(request, response);
- if (action === 'adminListUsers') return adminListUsers(request, response);
- if (action === 'adminCreateUser') return adminCreateUser(request, response);
- if (action === 'adminAdjustCredit') return adminAdjustCredit(request, response);
- response.json({ code: 400, success: false, error: `未知 action: ${action}` });
- } catch (error) {
- console.error('authCreditManager failed:', error.message, error.stack);
- response.json({ code: 500, success: false, error: error.message });
- }
- }
- async function ensureTables() {
- await Psql.query(`
- CREATE TABLE IF NOT EXISTS "AppUser" (
- "objectId" VARCHAR(50) PRIMARY KEY,
- "username" VARCHAR(120) UNIQUE NOT NULL,
- "email" VARCHAR(180) UNIQUE,
- "phone" VARCHAR(60) UNIQUE,
- "displayName" VARCHAR(180) DEFAULT '',
- "passwordHash" VARCHAR(255) NOT NULL,
- "passwordSalt" VARCHAR(120) NOT NULL,
- "role" VARCHAR(30) DEFAULT 'user',
- "status" VARCHAR(30) DEFAULT 'active',
- "createdAt" TIMESTAMPTZ DEFAULT NOW(),
- "updatedAt" TIMESTAMPTZ DEFAULT NOW()
- )
- `);
- await Psql.query(`CREATE INDEX IF NOT EXISTS idx_app_user_email ON "AppUser" ("email")`);
- await Psql.query(`CREATE INDEX IF NOT EXISTS idx_app_user_phone ON "AppUser" ("phone")`);
- await Psql.query(`
- CREATE TABLE IF NOT EXISTS "AppSession" (
- "token" VARCHAR(120) PRIMARY KEY,
- "userId" VARCHAR(50) NOT NULL,
- "expiresAt" TIMESTAMPTZ NOT NULL,
- "createdAt" TIMESTAMPTZ DEFAULT NOW()
- )
- `);
- await Psql.query(`
- CREATE TABLE IF NOT EXISTS "UserCreditAccount" (
- "userId" VARCHAR(50) PRIMARY KEY,
- "balance" NUMERIC(12,2) DEFAULT 0,
- "gifted" NUMERIC(12,2) DEFAULT 0,
- "totalRecharged" NUMERIC(12,2) DEFAULT 0,
- "totalConsumed" NUMERIC(12,2) DEFAULT 0,
- "createdAt" TIMESTAMPTZ DEFAULT NOW(),
- "updatedAt" TIMESTAMPTZ DEFAULT NOW()
- )
- `);
- await Psql.query(`
- CREATE TABLE IF NOT EXISTS "CreditLedger" (
- "objectId" VARCHAR(50) PRIMARY KEY,
- "userId" VARCHAR(50) NOT NULL,
- "type" VARCHAR(40) NOT NULL,
- "amount" NUMERIC(12,2) NOT NULL,
- "balanceAfter" NUMERIC(12,2) NOT NULL,
- "title" VARCHAR(255) DEFAULT '',
- "detail" JSONB DEFAULT '{}',
- "createdAt" TIMESTAMPTZ DEFAULT NOW()
- )
- `);
- await Psql.query(`CREATE INDEX IF NOT EXISTS idx_credit_ledger_user_time ON "CreditLedger" ("userId", "createdAt" DESC)`);
- await Psql.query(`
- CREATE TABLE IF NOT EXISTS "CreditReservation" (
- "objectId" VARCHAR(50) PRIMARY KEY,
- "userId" VARCHAR(50) NOT NULL,
- "operation" VARCHAR(120) NOT NULL,
- "cost" NUMERIC(12,2) NOT NULL,
- "status" VARCHAR(30) DEFAULT 'reserved',
- "ledgerId" VARCHAR(50),
- "detail" JSONB DEFAULT '{}',
- "createdAt" TIMESTAMPTZ DEFAULT NOW(),
- "updatedAt" TIMESTAMPTZ DEFAULT NOW()
- )
- `);
- await Psql.query(`
- CREATE TABLE IF NOT EXISTS "RechargeOrder" (
- "objectId" VARCHAR(50) PRIMARY KEY,
- "userId" VARCHAR(50) NOT NULL,
- "amountCny" NUMERIC(12,2) NOT NULL,
- "creditAmount" NUMERIC(12,2) NOT NULL,
- "status" VARCHAR(30) DEFAULT 'pending',
- "detail" JSONB DEFAULT '{}',
- "createdAt" TIMESTAMPTZ DEFAULT NOW(),
- "updatedAt" TIMESTAMPTZ DEFAULT NOW()
- )
- `);
- }
- async function register(request, response) {
- const username = clean(pickParam(request, 'username'));
- const email = clean(pickParam(request, 'email'));
- const phone = clean(pickParam(request, 'phone'));
- const displayName = clean(pickParam(request, 'displayName')) || username;
- const password = String(pickParam(request, 'password') || '');
- if (!username || !password) return response.json({ code: 400, success: false, error: '缺少用户名或密码' });
- const exists = await Psql.query(
- `SELECT "objectId" FROM "AppUser" WHERE "username"=$1 OR ($2 <> '' AND "email"=$2) OR ($3 <> '' AND "phone"=$3) LIMIT 1`,
- [username, email, phone]
- );
- if (exists.length) return response.json({ code: 409, success: false, error: '账号已存在' });
- const countRows = await Psql.query(`SELECT COUNT(*)::int AS count FROM "AppUser"`);
- const isFirstUser = Number(countRows[0].count || 0) === 0;
- const role = isFirstUser || ADMIN_IDENTIFIERS.includes(username) || ADMIN_IDENTIFIERS.includes(email) || ADMIN_IDENTIFIERS.includes(phone)
- ? 'admin'
- : 'user';
- const salt = generateId(16);
- const userId = generateId();
- const hash = passwordHash(password, salt);
- await Psql.query(
- `INSERT INTO "AppUser" ("objectId","username","email","phone","displayName","passwordHash","passwordSalt","role")
- VALUES ($1,$2,NULLIF($3,''),NULLIF($4,''),$5,$6,$7,$8)`,
- [userId, username, email, phone, displayName, hash, salt, role]
- );
- await Psql.query(
- `INSERT INTO "UserCreditAccount" ("userId","balance","gifted") VALUES ($1,$2,$2)`,
- [userId, REGISTER_GIFT_CREDITS]
- );
- await insertLedger(userId, 'gift', REGISTER_GIFT_CREDITS, REGISTER_GIFT_CREDITS, '注册赠送积分', {});
- const session = await createSession(userId);
- response.json({ code: 200, success: true, data: { token: session.token, user: await publicUser(userId) } });
- }
- async function login(request, response) {
- const identifier = clean(pickParam(request, 'identifier'));
- const password = String(pickParam(request, 'password') || '');
- const rows = await Psql.query(
- `SELECT * FROM "AppUser" WHERE "username"=$1 OR "email"=$1 OR "phone"=$1 LIMIT 1`,
- [identifier]
- );
- if (!rows.length) return response.json({ code: 401, success: false, error: '账号或密码不正确' });
- const row = rows[0];
- if (row.status !== 'active') return response.json({ code: 403, success: false, error: '账号已停用' });
- if (passwordHash(password, row.passwordSalt) !== row.passwordHash) {
- return response.json({ code: 401, success: false, error: '账号或密码不正确' });
- }
- const session = await createSession(row.objectId);
- response.json({ code: 200, success: true, data: { token: session.token, user: rowToUser(row) } });
- }
- async function me(request, response) {
- const session = await requireSession(request);
- response.json({ code: 200, success: true, data: session.user || await publicUser(session.userId) });
- }
- async function changePassword(request, response) {
- const session = await requireSession(request);
- const oldPassword = String(pickParam(request, 'oldPassword') || '');
- const newPassword = String(pickParam(request, 'newPassword') || '');
- if (!newPassword) return response.json({ code: 400, success: false, error: '缺少新密码' });
- const rows = await Psql.query(`SELECT * FROM "AppUser" WHERE "objectId"=$1 LIMIT 1`, [session.userId]);
- const row = rows[0];
- if (passwordHash(oldPassword, row.passwordSalt) !== row.passwordHash) {
- return response.json({ code: 400, success: false, error: '原密码不正确' });
- }
- const salt = generateId(16);
- await Psql.query(`UPDATE "AppUser" SET "passwordHash"=$1,"passwordSalt"=$2,"updatedAt"=NOW() WHERE "objectId"=$3`, [
- passwordHash(newPassword, salt), salt, session.userId
- ]);
- response.json({ code: 200, success: true, data: true });
- }
- async function balance(request, response) {
- const session = await requireSession(request);
- response.json({ code: 200, success: true, data: await getBalance(session.userId) });
- }
- async function ledger(request, response) {
- const session = await requireSession(request);
- const limit = Math.max(1, Math.min(200, Number(pickParam(request, 'limit') || 50)));
- const rows = await Psql.query(
- `SELECT * FROM "CreditLedger" WHERE "userId"=$1 ORDER BY "createdAt" DESC LIMIT ${limit}`,
- [session.userId]
- );
- response.json({ code: 200, success: true, data: rows.map(rowToLedger) });
- }
- async function reserve(request, response) {
- const session = await requireSession(request);
- const operation = clean(pickParam(request, 'operation')) || 'unknown';
- const cost = Number(pickParam(request, 'cost') || 0);
- const title = clean(pickParam(request, 'title')) || operation;
- const detail = pickParam(request, 'detail') || {};
- if (cost <= 0) return response.json({ code: 400, success: false, error: '积分消耗必须大于 0' });
- await Psql.query('BEGIN');
- try {
- const accounts = await Psql.query(`SELECT * FROM "UserCreditAccount" WHERE "userId"=$1 FOR UPDATE`, [session.userId]);
- if (!accounts.length || Number(accounts[0].balance || 0) < cost) {
- await Psql.query('ROLLBACK');
- return response.json({ code: 402, success: false, error: '积分不足,请先充值' });
- }
- const newBalance = Number(accounts[0].balance || 0) - cost;
- await Psql.query(
- `UPDATE "UserCreditAccount" SET "balance"=$1,"totalConsumed"="totalConsumed"+$2,"updatedAt"=NOW() WHERE "userId"=$3`,
- [newBalance, cost, session.userId]
- );
- const ledgerId = await insertLedger(session.userId, 'consume', -cost, newBalance, title, { operation, ...detail, status: 'reserved' });
- const reservationId = generateId();
- await Psql.query(
- `INSERT INTO "CreditReservation" ("objectId","userId","operation","cost","ledgerId","detail") VALUES ($1,$2,$3,$4,$5,$6)`,
- [reservationId, session.userId, operation, cost, ledgerId, JSON.stringify(detail || {})]
- );
- await Psql.query('COMMIT');
- response.json({ code: 200, success: true, data: { reservationId, balance: newBalance, cost } });
- } catch (e) {
- await Psql.query('ROLLBACK');
- throw e;
- }
- }
- async function commitReservation(request, response) {
- const session = await requireSession(request);
- const reservationId = clean(pickParam(request, 'reservationId'));
- await Psql.query(
- `UPDATE "CreditReservation" SET "status"='committed',"detail"=COALESCE("detail",'{}'::jsonb) || $1::jsonb,"updatedAt"=NOW()
- WHERE "objectId"=$2 AND "userId"=$3 AND "status"='reserved'`,
- [JSON.stringify(pickParam(request, 'detail') || {}), reservationId, session.userId]
- );
- response.json({ code: 200, success: true, data: true });
- }
- async function refundReservation(request, response) {
- const session = await requireSession(request);
- const reservationId = clean(pickParam(request, 'reservationId'));
- const reason = clean(pickParam(request, 'reason')) || '任务失败退回积分';
- await refundByReservation(session.userId, reservationId, reason);
- response.json({ code: 200, success: true, data: true });
- }
- async function createRechargeOrder(request, response) {
- const session = await requireSession(request);
- const amountCny = Number(pickParam(request, 'amountCny') || 0);
- if (amountCny <= 0) return response.json({ code: 400, success: false, error: '充值金额必须大于 0' });
- const order = {
- objectId: generateId(),
- amountCny,
- creditAmount: Math.round(amountCny * CREDIT_PER_CNY),
- status: 'pending',
- createdAt: new Date().toISOString()
- };
- await Psql.query(
- `INSERT INTO "RechargeOrder" ("objectId","userId","amountCny","creditAmount","status","detail") VALUES ($1,$2,$3,$4,'pending',$5)`,
- [order.objectId, session.userId, order.amountCny, order.creditAmount, JSON.stringify({ creditPerCny: CREDIT_PER_CNY })]
- );
- response.json({ code: 200, success: true, data: order });
- }
- async function adminListUsers(request, response) {
- const session = await requireSession(request);
- await requireAdmin(session.userId);
- const rows = await Psql.query(`
- SELECT u."objectId",u."username",u."email",u."phone",u."displayName",u."role",u."status",u."createdAt",
- COALESCE(c."balance",0) AS "creditBalance"
- FROM "AppUser" u
- LEFT JOIN "UserCreditAccount" c ON c."userId"=u."objectId"
- ORDER BY u."createdAt" DESC
- LIMIT 1000
- `);
- response.json({ code: 200, success: true, data: rows });
- }
- async function adminCreateUser(request, response) {
- const session = await requireSession(request);
- await requireAdmin(session.userId);
- const username = clean(pickParam(request, 'username'));
- const email = clean(pickParam(request, 'email'));
- const phone = clean(pickParam(request, 'phone'));
- const displayName = clean(pickParam(request, 'displayName')) || username;
- const password = String(pickParam(request, 'password') || '');
- const roleInput = clean(pickParam(request, 'role')) || 'user';
- const role = roleInput === 'admin' ? 'admin' : 'user';
- const initialCredits = Math.max(0, Number(pickParam(request, 'initialCredits') ?? REGISTER_GIFT_CREDITS));
- if (!username || !password) return response.json({ code: 400, success: false, error: '缺少用户名或初始密码' });
- const exists = await Psql.query(
- `SELECT "objectId" FROM "AppUser" WHERE "username"=$1 OR ($2 <> '' AND "email"=$2) OR ($3 <> '' AND "phone"=$3) LIMIT 1`,
- [username, email, phone]
- );
- if (exists.length) return response.json({ code: 409, success: false, error: '账号已存在' });
- await Psql.query('BEGIN');
- try {
- const salt = generateId(16);
- const userId = generateId();
- await Psql.query(
- `INSERT INTO "AppUser" ("objectId","username","email","phone","displayName","passwordHash","passwordSalt","role")
- VALUES ($1,$2,NULLIF($3,''),NULLIF($4,''),$5,$6,$7,$8)`,
- [userId, username, email, phone, displayName, passwordHash(password, salt), salt, role]
- );
- await Psql.query(
- `INSERT INTO "UserCreditAccount" ("userId","balance","gifted") VALUES ($1,$2,$2)`,
- [userId, initialCredits]
- );
- if (initialCredits > 0) {
- await insertLedger(userId, 'admin_adjust', initialCredits, initialCredits, '管理员发放初始积分', { adminUserId: session.userId });
- }
- await Psql.query('COMMIT');
- response.json({ code: 200, success: true, data: await publicUser(userId) });
- } catch (e) {
- await Psql.query('ROLLBACK');
- throw e;
- }
- }
- async function adminAdjustCredit(request, response) {
- const session = await requireSession(request);
- await requireAdmin(session.userId);
- const userId = clean(pickParam(request, 'userId'));
- const amount = Number(pickParam(request, 'amount') || 0);
- const note = clean(pickParam(request, 'note')) || '管理员调整';
- if (!userId || !amount) return response.json({ code: 400, success: false, error: '缺少用户或调整积分' });
- await Psql.query('BEGIN');
- try {
- await Psql.query(`INSERT INTO "UserCreditAccount" ("userId","balance") VALUES ($1,0) ON CONFLICT ("userId") DO NOTHING`, [userId]);
- const rows = await Psql.query(`SELECT * FROM "UserCreditAccount" WHERE "userId"=$1 FOR UPDATE`, [userId]);
- const next = Math.max(0, Number(rows[0].balance || 0) + amount);
- await Psql.query(`UPDATE "UserCreditAccount" SET "balance"=$1,"updatedAt"=NOW() WHERE "userId"=$2`, [next, userId]);
- await insertLedger(userId, 'admin_adjust', amount, next, note, { adminUserId: session.userId });
- await Psql.query('COMMIT');
- response.json({ code: 200, success: true, data: true });
- } catch (e) {
- await Psql.query('ROLLBACK');
- throw e;
- }
- }
- async function refundByReservation(userId, reservationId, reason) {
- await Psql.query('BEGIN');
- try {
- const rows = await Psql.query(
- `SELECT * FROM "CreditReservation" WHERE "objectId"=$1 AND "userId"=$2 FOR UPDATE`,
- [reservationId, userId]
- );
- if (!rows.length || rows[0].status !== 'reserved') {
- await Psql.query('COMMIT');
- return;
- }
- const cost = Number(rows[0].cost || 0);
- const accounts = await Psql.query(`SELECT * FROM "UserCreditAccount" WHERE "userId"=$1 FOR UPDATE`, [userId]);
- const next = Number(accounts[0].balance || 0) + cost;
- await Psql.query(
- `UPDATE "UserCreditAccount" SET "balance"=$1,"totalConsumed"=GREATEST(0,"totalConsumed"-$2),"updatedAt"=NOW() WHERE "userId"=$3`,
- [next, cost, userId]
- );
- await Psql.query(`UPDATE "CreditReservation" SET "status"='refunded',"updatedAt"=NOW() WHERE "objectId"=$1`, [reservationId]);
- await insertLedger(userId, 'refund', cost, next, reason, { reservationId });
- await Psql.query('COMMIT');
- } catch (e) {
- await Psql.query('ROLLBACK');
- throw e;
- }
- }
- async function requireLegacySession(request) {
- const token = clean(pickParam(request, 'sessionToken'));
- if (!token) throw new Error('请先登录');
- const rows = await Psql.query(
- `SELECT * FROM "AppSession" WHERE "token"=$1 AND "expiresAt" > NOW() LIMIT 1`,
- [token]
- );
- if (!rows.length) throw new Error('登录已过期,请重新登录');
- return rows[0];
- }
- async function requireAdmin(userId) {
- const user = await publicUser(userId);
- if (user.role !== 'admin') throw new Error('没有管理员权限');
- }
- async function createSession(userId) {
- const token = generateId(32);
- await Psql.query(
- `INSERT INTO "AppSession" ("token","userId","expiresAt") VALUES ($1,$2,NOW()+INTERVAL '30 days')`,
- [token, userId]
- );
- return { token };
- }
- async function legacyPublicUser(userId) {
- const rows = await Psql.query(`SELECT * FROM "AppUser" WHERE "objectId"=$1 LIMIT 1`, [userId]);
- if (!rows.length) throw new Error('用户不存在');
- return rowToUser(rows[0]);
- }
- async function requireSession(request) {
- const token = clean(pickParam(request, 'sessionToken'));
- if (!token) throw new Error('请先登录');
- const rows = await Psql.query(
- `SELECT * FROM "AppSession" WHERE "token"=$1 AND "expiresAt" > NOW() LIMIT 1`,
- [token]
- );
- if (rows.length) return rows[0];
- const parseUser = await verifyParseSession(token);
- if (!parseUser?.objectId) throw new Error('登录已过期,请重新登录');
- await ensureCreditAccount(parseUser.objectId, REGISTER_GIFT_CREDITS);
- return {
- token,
- userId: parseUser.objectId,
- user: parseUserToPublicUser(parseUser),
- source: 'parse',
- };
- }
- async function publicUser(userId) {
- const rows = await Psql.query(`SELECT * FROM "AppUser" WHERE "objectId"=$1 LIMIT 1`, [userId]);
- if (rows.length) return rowToUser(rows[0]);
- return {
- objectId: userId,
- username: userId,
- email: '',
- phone: '',
- displayName: userId,
- role: ADMIN_IDENTIFIERS.includes(userId) ? 'admin' : 'user'
- };
- }
- async function getBalance(userId) {
- await Psql.query(`INSERT INTO "UserCreditAccount" ("userId","balance") VALUES ($1,0) ON CONFLICT ("userId") DO NOTHING`, [userId]);
- const rows = await Psql.query(`SELECT * FROM "UserCreditAccount" WHERE "userId"=$1 LIMIT 1`, [userId]);
- const row = rows[0] || {};
- return {
- balance: Number(row.balance || 0),
- gifted: Number(row.gifted || 0),
- totalRecharged: Number(row.totalRecharged || 0),
- totalConsumed: Number(row.totalConsumed || 0)
- };
- }
- async function insertLedger(userId, type, amount, balanceAfter, title, detail) {
- const objectId = generateId();
- await Psql.query(
- `INSERT INTO "CreditLedger" ("objectId","userId","type","amount","balanceAfter","title","detail") VALUES ($1,$2,$3,$4,$5,$6,$7)`,
- [objectId, userId, type, amount, balanceAfter, title, JSON.stringify(detail || {})]
- );
- return objectId;
- }
- async function verifyParseSession(sessionToken) {
- if (typeof fetch !== 'function') return null;
- const resp = await fetch(`${PARSE_API_HOST}/parse/users/me?include=company`, {
- method: 'GET',
- headers: {
- 'X-Parse-Application-Id': PARSE_APP_ID,
- 'X-Parse-Session-Token': sessionToken,
- },
- });
- const data = await resp.json().catch(() => ({}));
- return resp.ok && data.objectId ? data : null;
- }
- async function ensureCreditAccount(userId, giftAmount) {
- const amount = Math.max(0, Number(giftAmount || 0));
- const rows = await Psql.query(`SELECT * FROM "UserCreditAccount" WHERE "userId"=$1 LIMIT 1`, [userId]);
- if (rows.length) return;
- await Psql.query(
- `INSERT INTO "UserCreditAccount" ("userId","balance","gifted") VALUES ($1,$2,$2)`,
- [userId, amount]
- );
- if (amount > 0) {
- await insertLedger(userId, 'gift', amount, amount, '初始赠送积分', { source: 'parse_user_login' });
- }
- }
- function parseUserToPublicUser(user) {
- const mobile = user.mobilePhoneNumber || user.mobile || '';
- return {
- objectId: user.objectId,
- username: user.username || mobile || user.objectId,
- email: user.email || '',
- phone: mobile,
- displayName: user.nickname || user.name || user.displayName || maskMobile(mobile) || user.username || user.objectId,
- role: user.role === 'admin' || user.isAdmin === true ? 'admin' : 'user',
- createdAt: user.createdAt
- };
- }
- function maskMobile(mobile) {
- const value = String(mobile || '');
- return /^1[3-9]\d{9}$/.test(value) ? value.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : value;
- }
- function rowToUser(row) {
- return {
- objectId: row.objectId,
- username: row.username,
- email: row.email || '',
- phone: row.phone || '',
- displayName: row.displayName || row.username,
- role: row.role || 'user',
- createdAt: row.createdAt
- };
- }
- function rowToLedger(row) {
- return {
- objectId: row.objectId,
- type: row.type,
- amount: Number(row.amount || 0),
- balanceAfter: Number(row.balanceAfter || 0),
- title: row.title || '',
- detail: typeof row.detail === 'string' ? JSON.parse(row.detail || '{}') : (row.detail || {}),
- createdAt: row.createdAt
- };
- }
- function passwordHash(password, salt) {
- // fmode 云函数环境不保证可用 crypto;这里使用轻量确定性哈希。
- // 后续若平台支持 crypto,应替换为 PBKDF2/bcrypt。
- let h = 2166136261;
- const input = `${salt}:${password}`;
- for (let i = 0; i < input.length; i++) {
- h ^= input.charCodeAt(i);
- h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
- }
- return (h >>> 0).toString(16);
- }
- function pickParam(request, ...names) {
- const sources = [request.params, request.body, request];
- for (const src of sources) {
- if (!src || typeof src !== 'object') continue;
- for (const n of names) {
- const v = src[n];
- if (v !== undefined && v !== null && v !== '') return v;
- }
- }
- return null;
- }
- function clean(v) {
- return String(v || '').trim();
- }
- function generateId(len) {
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- let s = '';
- const n = len || 10;
- for (let i = 0; i < n; i++) s += chars.charAt(Math.floor(Math.random() * chars.length));
- return s;
- }
|