feat: 更新 API 响应结构,统一使用 statusCode 和 errMsg 替代 code 和 msg;新增用户类型和公司 ID 字段;优化数据库索引;添加公司所有者同步测试脚本

This commit is contained in:
2026-03-29 19:57:04 +08:00
parent e9fe1165e3
commit 50c09d855b
50 changed files with 851 additions and 601 deletions

View File

@@ -54,7 +54,7 @@
**索引规划 (Indexes):**
* `CREATE UNIQUE INDEX` 针对 `company_id` (确保业务主键唯一)
* `CREATE INDEX` 针对 `company_usci` (加速信用代码检索)
* `CREATE INDEX` 针对 `company_owner_openid` (加速按公司所有者 openid 查询)
* `CREATE UNIQUE INDEX` 针对 `company_owner_openid` (限制同一公司所有者仅能绑定一个公司)
---

View File

@@ -9,7 +9,8 @@
"init:documents": "node pocketbase.documents.js",
"init:dictionary": "node pocketbase.dictionary.js",
"migrate:file-fields": "node pocketbase.file-fields-to-attachments.js",
"test:company-native-api": "node test-tbl-company-native-api.js"
"test:company-native-api": "node test-tbl-company-native-api.js",
"test:company-owner-sync": "node test-company-owner-sync.js"
},
"keywords": [],
"author": "",

View File

@@ -61,7 +61,7 @@ const collections = [
indexes: [
'CREATE UNIQUE INDEX idx_company_id ON tbl_company (company_id)',
'CREATE INDEX idx_company_usci ON tbl_company (company_usci)',
'CREATE INDEX idx_company_owner_openid ON tbl_company (company_owner_openid)'
'CREATE UNIQUE INDEX idx_company_owner_openid ON tbl_company (company_owner_openid)'
]
},
{

View File

@@ -37,6 +37,11 @@ const collections = [
{
name: 'tbl_auth_users',
type: 'auth',
listRule: '@request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB"',
viewRule: '@request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB"',
createRule: '',
updateRule: '',
deleteRule: '@request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB"',
fields: [
{ name: 'users_convers_id', type: 'text' },
{ name: 'openid', type: 'text', required: true },
@@ -196,6 +201,11 @@ async function createOrUpdateCollection(collectionData) {
const payload = {
name: collectionData.name,
type: collectionData.type,
listRule: Object.prototype.hasOwnProperty.call(collectionData, 'listRule') ? collectionData.listRule : null,
viewRule: Object.prototype.hasOwnProperty.call(collectionData, 'viewRule') ? collectionData.viewRule : null,
createRule: Object.prototype.hasOwnProperty.call(collectionData, 'createRule') ? collectionData.createRule : null,
updateRule: Object.prototype.hasOwnProperty.call(collectionData, 'updateRule') ? collectionData.updateRule : null,
deleteRule: Object.prototype.hasOwnProperty.call(collectionData, 'deleteRule') ? collectionData.deleteRule : null,
fields: collectionData.fields,
indexes: collectionData.indexes,
};

View File

@@ -0,0 +1,180 @@
import fs from 'node:fs/promises'
const runtimePath = new URL('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js', import.meta.url)
const runtimeText = await fs.readFile(runtimePath, 'utf8')
function readRuntimeValue(name) {
const match = runtimeText.match(new RegExp(`${name}:\\s*'([^']*)'`))
if (!match) {
throw new Error(`未在 runtime.js 中找到 ${name}`)
}
return match[1]
}
const baseUrl = readRuntimeValue('APP_BASE_URL')
const adminToken = readRuntimeValue('POCKETBASE_AUTH_TOKEN')
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
async function requestJson(path, options = {}) {
const res = await fetch(`${baseUrl}${path}`, options)
const text = await res.text()
let json = null
try {
json = text ? JSON.parse(text) : null
} catch {
throw new Error(`接口 ${path} 返回了非 JSON 响应:${text}`)
}
return { res, json, text }
}
async function findUserByOpenid(openid) {
const filter = encodeURIComponent(`openid="${openid}"`)
const result = await requestJson(`/pb/api/collections/tbl_auth_users/records?filter=${filter}&perPage=1&page=1&fields=id,openid,users_type,company_id`, {
headers: {
Authorization: `Bearer ${adminToken}`,
},
})
if (!result.res.ok) {
throw new Error(`查询用户失败:${result.text}`)
}
return result.json?.items?.[0] || null
}
async function patchUser(recordId, payload) {
const result = await requestJson(`/pb/api/collections/tbl_auth_users/records/${recordId}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${adminToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
if (!result.res.ok) {
throw new Error(`恢复用户失败:${result.text}`)
}
return result.json
}
async function deleteCompany(recordId) {
const result = await requestJson(`/pb/api/collections/tbl_company/records/${recordId}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${adminToken}`,
},
})
if (!result.res.ok) {
throw new Error(`删除测试公司失败:${result.text}`)
}
}
async function loginPlatform(loginAccount, password) {
const result = await requestJson('/pb/api/platform/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
login_account: loginAccount,
password,
}),
})
if (!result.res.ok || !result.json?.token) {
throw new Error(`平台登录失败:${result.text}`)
}
return result.json.token
}
async function createCompany(token, ownerOpenid) {
const ts = Date.now()
const body = {
company_name: `同步测试公司-${ts}`,
company_type: '渠道商',
company_entity: '测试人',
company_usci: `91310000${String(ts).slice(-10)}`,
company_nationality: '中国',
company_nationality_code: 'CN',
company_province: '上海',
company_province_code: '310000',
company_city: '上海',
company_city_code: '310100',
company_district: '浦东新区',
company_district_code: '310115',
company_postalcode: '200000',
company_add: '同步测试地址',
company_status: '有效',
company_level: 'A',
company_owner_openid: ownerOpenid,
company_remark: 'codex-sync-test',
}
const result = await requestJson('/pb/api/collections/tbl_company/records', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
if (!result.res.ok || !result.json?.id) {
throw new Error(`创建公司失败:${result.text}`)
}
return result.json
}
async function main() {
const openid = 'su13106859882'
const password = '13106859882'
const before = await findUserByOpenid(openid)
if (!before?.id) {
throw new Error(`未找到测试用户:${openid}`)
}
const token = await loginPlatform(password, password)
const createdCompany = await createCompany(token, openid)
await sleep(1500)
const after = await findUserByOpenid(openid)
const result = {
openid,
before_company_id: before.company_id || '',
before_users_type: before.users_type || '',
created_company_id: createdCompany.company_id || '',
created_company_record_id: createdCompany.id || '',
after_company_id: after?.company_id || '',
after_users_type: after?.users_type || '',
synced_company_id: after?.company_id === createdCompany.company_id,
synced_users_type: after?.users_type === '服务商',
}
try {
await patchUser(before.id, {
company_id: before.company_id || '',
users_type: before.users_type || '',
})
} finally {
await deleteCompany(createdCompany.id)
}
console.log(JSON.stringify(result, null, 2))
if (!result.synced_company_id || !result.synced_users_type) {
process.exitCode = 1
}
}
await main()