333 lines
12 KiB
JavaScript
Raw Permalink Normal View History

2026-08-12 15:20:58 +08:00
const { ObjectId } = require("mongodb");
const validator = require("../../utils/validator");
const Sm4Util = require("../../utils/sm4-util");
const hnHis = require("../hn-his");
const COLLECTION_NAME = "hlw-patient-self-auth";
const PATIENT_COLLECTION_NAME = "hlw-patient";
const AUTH_VALID_MS = 10 * 60 * 1000;
const RECORD_RETENTION_MS = 24 * 60 * 60 * 1000;
const indexPromises = new WeakMap();
async function removeLegacyAuthIdIndex(collection) {
try {
await collection.dropIndex("authId_1");
} catch (error) {
// 旧索引不存在时无需处理;其他数据库错误仍应暴露,避免留下隐蔽的唯一索引冲突。
if (error && (error.code === 27 || error.codeName === "IndexNotFound")) return;
throw error;
}
}
function ensureIndexes(db) {
if (!indexPromises.has(db)) {
const collection = db.collection(COLLECTION_NAME);
const promise = removeLegacyAuthIdIndex(collection)
.then(() => Promise.all([
collection.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }),
collection.createIndex({ ownerUserId: 1, status: 1 }),
]))
.catch((error) => {
indexPromises.delete(db);
throw error;
});
indexPromises.set(db, promise);
}
return indexPromises.get(db);
}
function parseRequestId(value) {
const id = typeof value === "string" ? value.trim() : "";
if (!/^[a-f\d]{24}$/i.test(id) || !ObjectId.isValid(id)) {
return { success: false, message: "授权链接无效" };
}
return {
success: true,
id,
objectId: new ObjectId(id),
};
}
function isExpired(requestTimestamp, now = Date.now()) {
return now - requestTimestamp > AUTH_VALID_MS;
}
function getCurrentUserId(context) {
const internalUserId = context && typeof context.userId === "string"
? context.userId.trim()
: "";
return ObjectId.isValid(internalUserId) ? internalUserId : "";
}
function decryptPatient(patient) {
return {
id: patient._id.toString(),
accountId: patient.accountId,
name: Sm4Util.decryptDataForSm3(patient.anotherName),
certNo: Sm4Util.decryptDataForSm3(patient.anotherIdNo),
mobile: Sm4Util.decryptDataForSm3(patient.anotherMobile),
address: typeof patient.address === "string" ? patient.address : "",
};
}
function maskCertNo(certNo) {
if (typeof certNo !== "string" || certNo.length <= 7) return "--";
return `${certNo.slice(0, 4)}${"*".repeat(certNo.length - 7)}${certNo.slice(-3)}`;
}
function getPersonInfo(certNo, now = new Date()) {
if (!validator.isChinaId(certNo)) return { age: "", sex: "" };
const birthYear = Number(certNo.slice(6, 10));
const birthMonth = Number(certNo.slice(10, 12));
const birthDay = Number(certNo.slice(12, 14));
const birthday = new Date(birthYear, birthMonth - 1, birthDay);
if (
birthday.getFullYear() !== birthYear
|| birthday.getMonth() + 1 !== birthMonth
|| birthday.getDate() !== birthDay
|| birthday > now
) {
return { age: "", sex: "" };
}
let age = now.getFullYear() - birthYear;
const birthdayPassed = now.getMonth() + 1 > birthMonth
|| (now.getMonth() + 1 === birthMonth && now.getDate() >= birthDay);
if (!birthdayPassed) age -= 1;
return {
age: Math.max(0, age),
sex: Number(certNo.charAt(16)) % 2 === 0 ? "女" : "男",
};
}
async function findRequest(db, parsed) {
return db.collection(COLLECTION_NAME).findOne({ _id: parsed.objectId });
}
async function createSelfAuthRequest(item, db, context) {
const patientId = item && typeof item.patientId === "string" ? item.patientId.trim() : "";
if (!ObjectId.isValid(patientId)) {
return { success: false, message: "就诊人ID格式不正确" };
}
try {
await ensureIndexes(db);
const ownerUserId = getCurrentUserId(context);
const accountId = item && typeof item.accountId === "string" ? item.accountId.trim() : "";
if (!ownerUserId || !accountId) return { success: false, message: "登录信息无效,请重新登录" };
const patient = await db.collection(PATIENT_COLLECTION_NAME).findOne({
_id: new ObjectId(patientId),
accountId,
disabled: false,
});
if (!patient) return { success: false, message: "就诊人不存在或无权操作" };
const requestTimestamp = Date.now();
const insertResult = await db.collection(COLLECTION_NAME).insertOne({
patientId: new ObjectId(patientId),
ownerAccountId: accountId,
ownerUserId,
requestTimestamp,
status: "pending",
attempts: 0,
createdAt: new Date(requestTimestamp),
updatedAt: new Date(requestTimestamp),
expiresAt: new Date(requestTimestamp + RECORD_RETENTION_MS),
});
return {
success: true,
message: "授权请求创建成功",
data: { id: insertResult.insertedId.toString() },
};
} catch (error) {
return { success: false, message: error.message || "授权请求创建失败" };
}
}
async function getSelfAuthPatient(item, db) {
const parsed = parseRequestId(item && item.id);
if (!parsed.success) return parsed;
try {
await ensureIndexes(db);
const request = await findRequest(db, parsed);
if (!request) return { success: false, message: "授权链接不存在或已失效" };
if (request.status !== "success" && isExpired(request.requestTimestamp)) {
return { success: false, message: "授权链接已过期,请重新发起授权" };
}
const patient = await db.collection(PATIENT_COLLECTION_NAME).findOne({
_id: request.patientId,
accountId: request.ownerAccountId,
disabled: false,
});
if (!patient) return { success: false, message: "就诊人不存在" };
const data = decryptPatient(patient);
const personInfo = getPersonInfo(data.certNo);
return {
success: true,
message: "获取就诊人成功",
data: {
name: data.name,
certNo: maskCertNo(data.certNo),
age: personInfo.age,
sex: personInfo.sex,
status: request.status,
},
};
} catch (error) {
return { success: false, message: error.message || "获取授权信息失败" };
}
}
async function submitSelfAuth(item, db, context) {
const parsed = parseRequestId(item && item.id);
if (!parsed.success) return parsed;
const psnToken = item && typeof item.psnToken === "string" ? item.psnToken.trim() : "";
if (!psnToken || psnToken.length > 2048) {
return { success: false, message: "医保授权信息无效" };
}
const collection = db.collection(COLLECTION_NAME);
try {
await ensureIndexes(db);
const request = await findRequest(db, parsed);
if (!request) return { success: false, message: "授权链接不存在或已失效" };
if (request.status === "success") {
return { success: true, message: "已完成授权", data: { status: "success" } };
}
if (isExpired(request.requestTimestamp)) {
return { success: false, message: "授权链接已过期,请重新发起授权" };
}
const authorizerUserId = getCurrentUserId(context);
if (!authorizerUserId) return { success: false, message: "登录信息无效,请重新登录" };
const claimed = await collection.findOneAndUpdate(
{ _id: parsed.objectId, status: { $in: ["pending", "failed"] } },
{
$set: {
status: "processing",
authorizerUserId,
errorMessage: "",
updatedAt: new Date(),
},
$inc: { attempts: 1 },
},
{ returnDocument: "after" }
);
if (!claimed) {
const latest = await collection.findOne({ _id: parsed.objectId });
if (latest && latest.status === "success") {
return { success: true, message: "已完成授权", data: { status: "success" } };
}
return { success: false, message: "授权正在处理中,请勿重复提交", data: { status: "processing" } };
}
const patientRecord = await db.collection(PATIENT_COLLECTION_NAME).findOne({
_id: claimed.patientId,
accountId: claimed.ownerAccountId,
disabled: false,
});
if (!patientRecord) throw new Error("就诊人不存在");
const patient = decryptPatient(patientRecord);
const hisResult = await hnHis({
type: "addHisCustomer",
idCard: patient.certNo,
name: patient.name,
mobile: patient.mobile,
address: patient.address,
psnToken,
});
const patients = Array.isArray(hisResult && hisResult.list) ? hisResult.list : [];
const hisArchive = patients.find((entry) => entry && entry.socialno === patient.certNo);
if (!hisResult || !hisResult.success || !hisArchive) {
throw new Error((hisResult && hisResult.message) || "医保建档失败,未找到匹配档案");
}
if (hisArchive.isyb !== "1") throw new Error("医保建档失败");
const result = {
...hisArchive,
socialno: hisArchive.socialno || patient.certNo,
tmbxx: Array.isArray(hisResult.tmbxx) ? hisResult.tmbxx : [],
};
const completedAt = new Date();
await collection.updateOne(
{ _id: parsed.objectId, status: "processing" },
{
$set: {
status: "success",
encryptedResult: Sm4Util.encryptDataForSm3(JSON.stringify(result)),
encryption: "SM4",
completedAt,
updatedAt: completedAt,
},
$unset: { errorMessage: "" },
}
);
return { success: true, message: "授权并建档成功", data: { status: "success" } };
} catch (error) {
await collection.updateOne(
{ _id: parsed.objectId, status: "processing" },
{ $set: { status: "failed", errorMessage: error.message || "医保建档失败", updatedAt: new Date() } }
).catch(() => { });
return { success: false, message: error.message || "医保建档失败", data: { status: "failed" } };
}
}
async function getSelfAuthResult(item, db, context) {
const parsed = parseRequestId(item && item.id);
console.log('parsed: ', parsed)
if (!parsed.success) return parsed;
try {
await ensureIndexes(db);
const ownerUserId = getCurrentUserId(context);
if (!ownerUserId) return { success: false, message: "登录信息无效,请重新登录" };
const request = await findRequest(db, parsed);
console.log('request: ', request)
if (!request || request.ownerUserId !== ownerUserId) {
return { success: false, message: "授权记录不存在或无权访问" };
}
if (request.status === "success") {
if (!request.encryptedResult || request.encryption !== "SM4") {
return { success: false, message: "授权结果无效" };
}
const hisArchive = JSON.parse(Sm4Util.decryptDataForSm3(request.encryptedResult));
return { success: true, message: "授权成功", data: { status: "success", hisArchive } };
}
if (request.status !== "processing" && isExpired(request.requestTimestamp)) {
return { success: true, message: "授权链接已过期", data: { status: "expired" } };
}
return {
success: true,
message: request.errorMessage || "等待就诊人授权",
data: { status: request.status, errorMessage: request.errorMessage || "" },
};
} catch (error) {
return { success: false, message: error.message || "查询授权结果失败" };
}
}
module.exports = async (item, db, context) => {
switch (item.type) {
case "createSelfAuthRequest":
return createSelfAuthRequest(item, db, context);
case "getSelfAuthPatient":
return getSelfAuthPatient(item, db);
case "submitSelfAuth":
return submitSelfAuth(item, db, context);
case "getSelfAuthResult":
return getSelfAuthResult(item, db, context);
default:
return { success: false, message: "未找到本人授权接口" };
}
};
module.exports.parseRequestId = parseRequestId;
module.exports.maskCertNo = maskCertNo;
module.exports.getPersonInfo = getPersonInfo;
module.exports.constants = { AUTH_VALID_MS, RECORD_RETENTION_MS, COLLECTION_NAME };