|
- const Base = require('./base');
- const jwt = require('jsonwebtoken');
- const COS = require('cos-nodejs-sdk-v5');
- const fs = require('fs');
- const path = require('path');
- const cosConfig = require('../config/cos.js');
-
- const APP_REMARK = 'pap_mini_cytx';
-
- module.exports = class extends Base {
- // POST /api/mp/login
- async loginAction() {
- const code = this.post('code');
- if (!code) return this.json({ code: 1, msg: '缺少code参数' });
- try {
- const wechatService = this.service('wechat');
- const session = await wechatService.code2Session(code, APP_REMARK);
- const { openid, unionid } = session;
- if (!openid) return this.json({ code: 1, msg: '获取openid失败' });
-
- const userModel = this.model('wechat_user');
- let user = await userModel.findByOpenId(openid, APP_REMARK);
- if (think.isEmpty(user)) {
- const id = await userModel.createUser({
- open_id: openid, union_id: unionid || '', app_remark: APP_REMARK,
- nickname: '微信用户', status: 1
- });
- user = await userModel.where({ id }).find();
- }
- if (user.status !== 1) return this.json({ code: 1, msg: '账号已被停用' });
-
- const token = jwt.sign(
- { id: user.id, open_id: user.open_id, type: 'mp' },
- Base.JWT_SECRET, { expiresIn: 7 * 24 * 60 * 60 }
- );
- let patient = null;
- if (user.patient_id) {
- patient = await this.model('patient')
- .field('id, patient_no, name, phone, status, auth_status')
- .where({ id: user.patient_id, is_deleted: 0 }).find();
- if (think.isEmpty(patient)) patient = null;
- }
- return this.json({ code: 0, data: { token, userInfo: {
- id: user.id, nickname: user.nickname || '', avatar: user.avatar || '',
- phone: user.phone || '', patient_id: user.patient_id || null, patient
- }}});
- } catch (error) {
- think.logger.error('login error:', error);
- return this.json({ code: 1, msg: error.message || '登录失败' });
- }
- }
-
- // POST /api/mp/phoneLogin - H5 手机号验证码登录
- async phoneLoginAction() {
- const { mobile, code } = this.post();
- if (!mobile || !/^1[3-9]\d{9}$/.test(mobile)) {
- return this.json({ code: 1, msg: '请输入正确的手机号' });
- }
- if (!code || !/^\d{6}$/.test(code)) {
- return this.json({ code: 1, msg: '请输入6位验证码' });
- }
- const verifyResult = await this.verifySmsCode(mobile, 'login', code);
- if (!verifyResult.success) return this.json({ code: 1, msg: verifyResult.message });
-
- try {
- const userModel = this.model('wechat_user');
- // 查找已有的 H5 用户(open_id 以 h5_ 开头)
- let user = await userModel.where({
- open_id: 'h5_' + mobile, app_remark: APP_REMARK, status: 1
- }).find();
-
- if (think.isEmpty(user)) {
- // 没有 H5 用户记录,创建一条新的
- // 同时查找该手机号是否已有 patient(可能在小程序端已认证)
- let patientId = null;
- const patient = await this.model('patient').where({ phone: mobile, is_deleted: 0 }).find();
- if (!think.isEmpty(patient)) patientId = patient.id;
-
- const id = await userModel.createUser({
- open_id: 'h5_' + mobile, union_id: '', app_remark: APP_REMARK,
- nickname: '', phone: mobile, patient_id: patientId, status: 1
- });
- user = await userModel.where({ id }).find();
- }
- if (user.status !== 1) return this.json({ code: 1, msg: '账号已被停用' });
-
- const token = jwt.sign(
- { id: user.id, open_id: user.open_id || '', type: 'mp' },
- Base.JWT_SECRET, { expiresIn: 7 * 24 * 60 * 60 }
- );
- let patient = null;
- if (user.patient_id) {
- patient = await this.model('patient')
- .field('id, patient_no, name, phone, status, auth_status')
- .where({ id: user.patient_id, is_deleted: 0 }).find();
- if (think.isEmpty(patient)) patient = null;
- }
- return this.json({ code: 0, data: { token, userInfo: {
- id: user.id, nickname: user.nickname || '', avatar: user.avatar || '',
- phone: user.phone || mobile, patient_id: user.patient_id || null, patient
- }}});
- } catch (error) {
- think.logger.error('phoneLogin error:', error);
- return this.json({ code: 1, msg: error.message || '登录失败' });
- }
- }
-
- // GET /api/mp/userinfo
- async userinfoAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- try {
- const user = await this.model('wechat_user').where({ id: mpUser.id, status: 1 }).find();
- if (think.isEmpty(user)) return this.json({ code: 1009, msg: '用户不存在' });
- let patient = null;
- if (user.patient_id) {
- patient = await this.model('patient')
- .field('id, patient_no, name, phone, status, auth_status')
- .where({ id: user.patient_id, is_deleted: 0 }).find();
- if (think.isEmpty(patient)) patient = null;
- }
- return this.json({ code: 0, data: {
- id: user.id, nickname: user.nickname || '', avatar: user.avatar || '',
- phone: user.phone || '', patient_id: user.patient_id || null, patient
- }});
- } catch (error) {
- think.logger.error('userinfo error:', error);
- return this.json({ code: 1, msg: '获取用户信息失败' });
- }
- }
-
- // POST /api/mp/upload
- async uploadAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
- let file = this.file('file');
- let filePath = file ? file.path : '';
- if (!filePath) {
- await sleep(1000);
- file = this.file('file');
- filePath = file ? file.path : '';
- if (!filePath) return this.json({ code: 1, msg: '请选择要上传的文件' });
- }
- try {
- const originalName = file.name;
- const mimeType = file.type;
- const now = new Date();
- const dateFolder = now.getFullYear() + '/' + String(now.getMonth() + 1).padStart(2, '0') + '/' + String(now.getDate()).padStart(2, '0');
- const ext = path.extname(originalName);
- const baseName = path.basename(originalName, ext);
- const fileName = baseName + '_' + Date.now() + ext;
- const cosKey = 'uploads/' + dateFolder + '/' + fileName;
- const cos = new COS({ SecretId: cosConfig.secretId, SecretKey: cosConfig.secretKey });
- const uploadResult = await new Promise((resolve, reject) => {
- cos.putObject({
- Bucket: cosConfig.bucket, Region: cosConfig.region, Key: cosKey,
- Body: fs.createReadStream(filePath), ContentType: mimeType
- }, (err, data) => { if (err) reject(err); else resolve(data); });
- });
- setTimeout(() => { try { if (fs.existsSync(filePath)) fs.unlinkSync(filePath); } catch (e) {} }, 1000);
- if (uploadResult.statusCode === 200) {
- const bucketUrl = 'https://' + uploadResult.Location;
- const cdnUrl = bucketUrl.replace(cosConfig.bucketUrl, cosConfig.cdnUrl);
- return this.json({ code: 0, data: { url: cdnUrl } });
- }
- return this.json({ code: 1, msg: '文件上传失败' });
- } catch (error) {
- think.logger.error('upload error:', error);
- if (file && file.path && fs.existsSync(file.path)) { try { fs.unlinkSync(file.path); } catch (e) {} }
- return this.json({ code: 1, msg: '文件上传失败: ' + error.message });
- }
- }
- // POST /api/mp/sendSmsCode
- async sendSmsCodeAction() {
- const { mobile, bizType = 'real_name_auth' } = this.post();
- // login 场景不需要登录态
- if (bizType !== 'login') {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- }
- if (!mobile || !/^1[3-9]\d{9}$/.test(mobile)) {
- return this.json({ code: 1, msg: '请输入正确的手机号' });
- }
- const result = await this.sendSmsCode(mobile, bizType);
- if (!result.success) return this.json({ code: 1, msg: result.message });
- const smsConfig = require('../config/sms.js');
- const data = smsConfig.enabled ? {} : { code: result.code };
- return this.json({ code: 0, data, msg: result.message });
- }
-
- // GET /api/mp/authInfo
- async authInfoAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- try {
- const user = await this.model('wechat_user').where({ id: mpUser.id, status: 1 }).find();
- if (think.isEmpty(user) || !user.patient_id) return this.json({ code: 0, data: { authStatus: 0 } });
- const patient = await this.model('patient').where({ id: user.patient_id, is_deleted: 0 }).find();
- if (think.isEmpty(patient)) return this.json({ code: 0, data: { authStatus: 0 } });
- return this.json({ code: 0, data: {
- authStatus: patient.auth_status || 0,
- idCardType: patient.id_card_type || 1,
- idCardFront: patient.id_card_front || '',
- idCardBack: patient.id_card_back || '',
- photo: patient.photo || '',
- realName: patient.name || '',
- idCard: patient.id_card || '',
- gender: patient.gender || '',
- birthday: patient.birth_date || '',
- issuingAuthority: patient.issuing_authority || '',
- validPeriod: patient.valid_period || '',
- phone: patient.phone || '',
- authTime: patient.auth_time || ''
- }});
- } catch (error) {
- think.logger.error('authInfo error:', error);
- return this.json({ code: 1, msg: '获取认证信息失败' });
- }
- }
- // POST /api/mp/authSubmit
- async authSubmitAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- const { idCardType, idCardFront, idCardBack, photo, realName, idCard, gender, birthday, issuingAuthority, validPeriod, mobile, code, confirmBind } = this.post();
- if (!realName) return this.json({ code: 1, msg: '请输入证件姓名' });
- if (!idCard) return this.json({ code: 1, msg: '请输入证件号码' });
- const cardTypeInt = parseInt(idCardType) || 1;
- if (cardTypeInt === 2) {
- if (!photo) return this.json({ code: 1, msg: '请上传免冠照片' });
- } else {
- if (!idCardFront) return this.json({ code: 1, msg: '请上传证件正面照片' });
- if (!idCardBack) return this.json({ code: 1, msg: '请上传证件反面照片' });
- }
- if (!mobile || !/^1[3-9]\d{9}$/.test(mobile)) return this.json({ code: 1, msg: '请输入正确的手机号' });
-
- // 确认绑定时跳过验证码校验(第一次提交时已校验过)
- if (!confirmBind) {
- if (!code || !/^\d{6}$/.test(code)) return this.json({ code: 1, msg: '请输入6位验证码' });
- const verifyResult = await this.verifySmsCode(mobile, 'real_name_auth', code);
- if (!verifyResult.success) return this.json({ code: 1, msg: verifyResult.message });
- }
-
- const patientModel = this.model('patient');
- const userModel = this.model('wechat_user');
- const currentUser = await userModel.where({ id: mpUser.id }).find();
-
- // 排除当前用户已绑定的 patient
- const excludeId = currentUser.patient_id || 0;
-
- // 查找身份证是否已存在
- const idCardCondition = { id_card: idCard, is_deleted: 0 };
- if (excludeId) idCardCondition.id = ['!=', excludeId];
- const existByIdCard = await patientModel.where(idCardCondition).find();
-
- // 查找手机号是否已存在
- const phoneCondition = { phone: mobile, is_deleted: 0 };
- if (excludeId) phoneCondition.id = ['!=', excludeId];
- const existByPhone = await patientModel.where(phoneCondition).find();
-
- if (!think.isEmpty(existByIdCard)) {
- if (existByIdCard.phone === mobile) {
- // 手机号+身份证都匹配 → 可绑定已有患者
- if (!confirmBind) {
- const maskedName = existByIdCard.name.length > 1
- ? existByIdCard.name[0] + '*'.repeat(existByIdCard.name.length - 1)
- : existByIdCard.name;
- const maskedPhone = '****' + existByIdCard.phone.slice(-4);
- return this.json({
- code: 1010,
- data: { patientName: maskedName, patientPhone: maskedPhone },
- msg: '该用户信息已存在'
- });
- }
- // 用户确认绑定
- const boundUser = await userModel.where({
- patient_id: existByIdCard.id,
- id: ['!=', mpUser.id],
- open_id: ['NOT LIKE', 'h5_%'],
- status: 1
- }).find();
- if (!think.isEmpty(boundUser)) {
- return this.json({ code: 1, msg: '该患者信息已被其他微信账号绑定' });
- }
- const now = think.datetime(new Date());
- await userModel.where({ id: mpUser.id }).update({ patient_id: existByIdCard.id, update_time: now });
- await patientModel.where({ id: existByIdCard.id }).update({
- name: realName, phone: mobile, id_card_type: cardTypeInt,
- id_card_front: idCardFront || '', id_card_back: idCardBack || '', photo: photo || '',
- gender: gender || '', birth_date: birthday || null,
- issuing_authority: issuingAuthority || '', valid_period: validPeriod || '',
- auth_status: 1, auth_time: now, update_time: now
- });
- return this.json({ code: 0, data: {}, msg: '实名认证成功' });
- }
- // 身份证存在但手机号不同
- return this.json({ code: 1, msg: '该证件号已被其他用户认证' });
- }
-
- // 身份证不存在,但手机号已被其他患者使用
- if (!think.isEmpty(existByPhone)) {
- return this.json({ code: 1, msg: '该手机号已被其他患者使用' });
- }
-
- if (cardTypeInt === 1 || cardTypeInt === 3) {
- const faceidConfig = require('../config/faceid.js');
- if (faceidConfig.enabled) {
- const verifyIdResult = await this._verifyIdCard(realName, idCard);
- if (!verifyIdResult.success) return this.json({ code: 1, msg: verifyIdResult.message });
- }
- }
-
- const now = think.datetime(new Date());
- try {
- if (currentUser.patient_id) {
- await patientModel.where({ id: currentUser.patient_id }).update({
- name: realName, phone: mobile, id_card: idCard, id_card_type: cardTypeInt,
- id_card_front: idCardFront || '', id_card_back: idCardBack || '', photo: photo || '',
- gender: gender || '', birth_date: birthday || null,
- issuing_authority: issuingAuthority || '', valid_period: validPeriod || '',
- auth_status: 1, auth_time: now, update_time: now
- });
- } else {
- const patientNo = patientModel.generatePatientNo();
- const patientId = await patientModel.add({
- patient_no: patientNo, name: realName, phone: mobile, id_card: idCard,
- id_card_type: cardTypeInt, id_card_front: idCardFront || '', id_card_back: idCardBack || '',
- photo: photo || '', gender: gender || '', birth_date: birthday || null,
- issuing_authority: issuingAuthority || '', valid_period: validPeriod || '',
- auth_status: 1, auth_time: now, status: -1, is_deleted: 0, create_time: now, update_time: now
- });
- await userModel.where({ id: mpUser.id }).update({ patient_id: patientId, update_time: now });
- }
- return this.json({ code: 0, data: {}, msg: '实名认证成功' });
- } catch (error) {
- think.logger.error('authSubmit error:', error);
- return this.json({ code: 1, msg: '认证失败: ' + error.message });
- }
- }
- // GET /api/mp/myInfo
- async myInfoAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- try {
- const user = await this.model('wechat_user').where({ id: mpUser.id, status: 1 }).find();
- if (think.isEmpty(user) || !user.patient_id) return this.json({ code: 0, data: null });
- const patient = await this.model('patient').where({ id: user.patient_id, is_deleted: 0 }).find();
- if (think.isEmpty(patient)) return this.json({ code: 0, data: null });
-
- const codes = [patient.province_code, patient.city_code, patient.district_code].filter(Boolean);
- const regionMap = {};
- if (codes.length) {
- const regions = await this.model('sys_region').where({ code: ['in', codes] }).select();
- regions.forEach(r => { regionMap[r.code] = r.name; });
- }
- let documents = [];
- try { documents = JSON.parse(patient.documents || '[]'); } catch (e) { documents = []; }
- let samplePhotos = [];
- try { samplePhotos = JSON.parse(patient.sample_photos || '[]'); } catch (e) { samplePhotos = []; }
- let sampleTypes = [];
- try { sampleTypes = JSON.parse(patient.sample_types || '[]'); } catch (e) { sampleTypes = []; }
-
- // 如果是驳回状态,查最近一条驳回原因
- let rejectReason = '';
- if (patient.status === 2) {
- const audit = await this.model('patient_audit')
- .where({ patient_id: user.patient_id, action: 'reject' })
- .order('id DESC')
- .find();
- if (!think.isEmpty(audit)) rejectReason = audit.reason || '';
- }
-
- return this.json({ code: 0, data: {
- name: patient.name || '', id_card: patient.id_card || '', phone: patient.phone || '',
- gender: patient.gender || '', birth_date: patient.birth_date || '',
- province_code: patient.province_code || '', city_code: patient.city_code || '',
- district_code: patient.district_code || '',
- province_name: regionMap[patient.province_code] || '',
- city_name: regionMap[patient.city_code] || '',
- district_name: regionMap[patient.district_code] || '',
- address: patient.address || '',
- hospital: patient.hospital || '',
- emergency_contact: patient.emergency_contact || '',
- emergency_phone: patient.emergency_phone || '',
- tag: patient.tag || '', documents,
- sample_types: sampleTypes,
- wax_return: patient.wax_return || 0,
- return_name: patient.return_name || '',
- return_phone: patient.return_phone || '',
- return_province_code: patient.return_province_code || '',
- return_city_code: patient.return_city_code || '',
- return_district_code: patient.return_district_code || '',
- return_address: patient.return_address || '',
- report_email: patient.report_email || '',
- sample_tracking_no: patient.sample_tracking_no || '',
- sample_photos: samplePhotos,
- sign_income: patient.sign_income || '',
- sign_privacy: patient.sign_privacy || '',
- sign_privacy_jhr: patient.sign_privacy_jhr || '',
- sign_promise: patient.sign_promise || '',
- income_amount: patient.income_amount || '',
- guardian_name: patient.guardian_name || '',
- guardian_id_card: patient.guardian_id_card || '',
- guardian_relation: patient.guardian_relation || '',
- status: patient.status, auth_status: patient.auth_status || 0,
- reject_reason: rejectReason
- }});
- } catch (error) {
- think.logger.error('myInfo error:', error);
- return this.json({ code: 1, msg: '获取资料失败' });
- }
- }
-
- // POST /api/mp/saveMyInfo
- async saveMyInfoAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- const { gender, province_code, city_code, district_code, address, hospital, emergency_contact, emergency_phone, documents, tag, sample_types, wax_return, return_name, return_phone, return_province_code, return_city_code, return_district_code, return_address, report_email, sample_tracking_no, sample_photos, sign_income, sign_privacy, sign_privacy_jhr, sign_promise, income_amount, guardian_name, guardian_id_card, guardian_relation, mp_env_version } = this.post();
- const user = await this.model('wechat_user').where({ id: mpUser.id, status: 1 }).find();
- if (think.isEmpty(user) || !user.patient_id) return this.json({ code: 1, msg: '请先完成实名认证' });
- if (!province_code || !city_code || !district_code) return this.json({ code: 1, msg: '请选择省市区' });
- if (!address) return this.json({ code: 1, msg: '请填写详细地址' });
- if (!emergency_contact || !emergency_phone) return this.json({ code: 1, msg: '请填写紧急联系人信息' });
-
- const now = think.datetime(new Date());
- try {
- // 更新小程序版本标识(用于订阅消息推送到对应版本)
- if (mp_env_version) {
- await this.model('wechat_user').where({ id: mpUser.id }).update({ mp_env_version });
- }
- await this.model('patient').where({ id: user.patient_id }).update({
- gender: gender || '', province_code: province_code || '', city_code: city_code || '',
- district_code: district_code || '', address: address || '',
- hospital: hospital || '',
- emergency_contact: emergency_contact || '', emergency_phone: emergency_phone || '',
- tag: tag || '',
- documents: JSON.stringify(documents || []),
- sample_types: JSON.stringify(sample_types || []),
- wax_return: (sample_types && sample_types.length && wax_return) ? 1 : 0,
- return_name: (sample_types && sample_types.length && wax_return) ? (return_name || '') : '',
- return_phone: (sample_types && sample_types.length && wax_return) ? (return_phone || '') : '',
- return_province_code: (sample_types && sample_types.length && wax_return) ? (return_province_code || '') : '',
- return_city_code: (sample_types && sample_types.length && wax_return) ? (return_city_code || '') : '',
- return_district_code: (sample_types && sample_types.length && wax_return) ? (return_district_code || '') : '',
- return_address: (sample_types && sample_types.length && wax_return) ? (return_address || '') : '',
- report_email: (sample_types && sample_types.length) ? (report_email || '') : '',
- sample_tracking_no: (sample_types && sample_types.length) ? (sample_tracking_no || '') : '',
- sample_photos: (sample_types && sample_types.length) ? JSON.stringify(sample_photos || []) : '[]',
- sign_income: sign_income || '',
- sign_privacy: sign_privacy || '',
- sign_privacy_jhr: sign_privacy_jhr || '',
- sign_promise: sign_promise || '',
- income_amount: income_amount || null,
- guardian_name: guardian_name || '',
- guardian_id_card: guardian_id_card || '',
- guardian_relation: guardian_relation || '',
- status: 0,
- update_time: now
- });
- // 记录提交审核日志
- await this.model('patient_audit').add({
- patient_id: user.patient_id,
- action: 'submit',
- operator_id: 0,
- operator_name: '用户自助提交'
- });
-
- return this.json({ code: 0, data: {}, msg: '提交成功' });
- } catch (error) {
- think.logger.error('saveMyInfo error:', error);
- return this.json({ code: 1, msg: '保存失败: ' + error.message });
- }
- }
- // POST /api/mp/changePhone
- async changePhoneAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- const { mobile, code } = this.post();
- if (!mobile || !/^1[3-9]\d{9}$/.test(mobile)) return this.json({ code: 1, msg: '请输入正确的手机号' });
- if (!code || !/^\d{6}$/.test(code)) return this.json({ code: 1, msg: '请输入6位验证码' });
- const verifyResult = await this.verifySmsCode(mobile, 'change_phone', code);
- if (!verifyResult.success) return this.json({ code: 1, msg: verifyResult.message });
- const now = think.datetime(new Date());
- const userModel = this.model('wechat_user');
- try {
- await userModel.where({ id: mpUser.id }).update({ phone: mobile, update_time: now });
- const user = await userModel.where({ id: mpUser.id }).find();
- if (user.patient_id) {
- await this.model('patient').where({ id: user.patient_id }).update({ phone: mobile, update_time: now });
- }
- return this.json({ code: 0, data: {}, msg: '手机号修改成功' });
- } catch (error) {
- think.logger.error('changePhone error:', error);
- return this.json({ code: 1, msg: '修改失败: ' + error.message });
- }
- }
-
- // POST /api/mp/sign - 签署协议,生成合成图返回URL(不写库)
- async signAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- const { type, signImage, amount, guardianName, guardianIdCard, guardianRelation } = this.post();
- const validTypes = ['income', 'privacy', 'privacy_jhr', 'promise'];
- if (!validTypes.includes(type)) return this.json({ code: 1, msg: '签署类型错误' });
- if (!signImage) return this.json({ code: 1, msg: '请先签名' });
- if (type === 'income' && (!amount || Number(amount) <= 0)) {
- return this.json({ code: 1, msg: '请填写有效的收入金额' });
- }
- if (type === 'privacy_jhr') {
- if (!guardianName) return this.json({ code: 1, msg: '请输入监护人姓名' });
- if (!guardianIdCard) return this.json({ code: 1, msg: '请输入监护人身份证号' });
- if (!guardianRelation) return this.json({ code: 1, msg: '请输入与患者关系' });
- }
-
- try {
- // 获取患者姓名
- const user = await this.model('wechat_user').where({ id: mpUser.id, status: 1 }).find();
- if (think.isEmpty(user) || !user.patient_id) return this.json({ code: 1, msg: '请先完成实名认证' });
- const patient = await this.model('patient').field('name, id_card').where({ id: user.patient_id, is_deleted: 0 }).find();
- if (think.isEmpty(patient)) return this.json({ code: 1, msg: '患者信息不存在' });
-
- // 获取协议内容
- const contentKey = 'sign_' + type;
- const doc = await this.model('content').getByKey(contentKey);
- if (think.isEmpty(doc)) return this.json({ code: 1, msg: '协议内容未配置' });
-
- const signTime = think.datetime(new Date());
-
- // 调用截图服务生成合成图
- const screenshotService = this.service('screenshot');
- const generateParams = {
- title: doc.title,
- content: doc.content,
- signImageUrl: signImage,
- signerName: patient.name,
- signerIdCard: patient.id_card,
- signTime,
- amount: type === 'income' ? amount : null
- };
- // 监护人类型传递额外字段
- if (type === 'privacy_jhr') {
- generateParams.guardianName = guardianName;
- generateParams.guardianIdCard = guardianIdCard;
- generateParams.guardianRelation = guardianRelation;
- }
- const url = await screenshotService.generate(generateParams);
-
- return this.json({ code: 0, data: { url }, msg: '签署成功' });
- } catch (error) {
- think.logger.error('sign error:', error);
- return this.json({ code: 1, msg: '签署失败: ' + error.message });
- }
- }
-
- // POST /api/mp/updateAvatar - 更新头像
- async updateAvatarAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- const { avatar } = this.post();
- if (!avatar) return this.json({ code: 1, msg: '请上传头像' });
- const now = think.datetime(new Date());
- try {
- await this.model('wechat_user').where({ id: mpUser.id }).update({ avatar, update_time: now });
- return this.json({ code: 0, data: { avatar }, msg: '头像更新成功' });
- } catch (error) {
- think.logger.error('updateAvatar error:', error);
- return this.json({ code: 1, msg: '更新失败: ' + error.message });
- }
- }
-
- // GET /api/mp/messages - 消息列表
- async messagesAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- const { page = 1, pageSize = 20 } = this.get();
- try {
- const user = await this.model('wechat_user').where({ id: mpUser.id, status: 1 }).find();
- if (think.isEmpty(user) || !user.patient_id) return this.json({ code: 0, data: { data: [], count: 0, totalPages: 0, currentPage: 1 } });
- const list = await this.model('message')
- .where({ patient_id: user.patient_id })
- .order('id DESC')
- .page(page, pageSize)
- .countSelect();
- return this.json({ code: 0, data: list });
- } catch (error) {
- think.logger.error('messages error:', error);
- return this.json({ code: 1, msg: '获取消息失败' });
- }
- }
-
- // GET /api/mp/messageDetail - 消息详情(同时标记已读)
- async messageDetailAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- const { id } = this.get();
- if (!id) return this.json({ code: 1, msg: '参数错误' });
- try {
- const user = await this.model('wechat_user').where({ id: mpUser.id, status: 1 }).find();
- if (think.isEmpty(user) || !user.patient_id) return this.json({ code: 1, msg: '无权访问' });
- const msg = await this.model('message').where({ id, patient_id: user.patient_id }).find();
- if (think.isEmpty(msg)) return this.json({ code: 1, msg: '消息不存在' });
- // 标记已读
- if (!msg.is_read) {
- await this.model('message').where({ id }).update({ is_read: 1 });
- }
- // 查患者姓名
- const patient = await this.model('patient').field('name').where({ id: user.patient_id }).find();
- msg.patient_name = patient ? patient.name : '';
- return this.json({ code: 0, data: msg });
- } catch (error) {
- think.logger.error('messageDetail error:', error);
- return this.json({ code: 1, msg: '获取消息详情失败' });
- }
- }
-
- // GET /api/mp/unreadCount - 未读消息数
- async unreadCountAction() {
- const mpUser = this.mpUser;
- if (!mpUser) return this.json({ code: 1009, msg: '请先登录' });
- try {
- const user = await this.model('wechat_user').where({ id: mpUser.id, status: 1 }).find();
- if (think.isEmpty(user) || !user.patient_id) return this.json({ code: 0, data: { count: 0 } });
- const count = await this.model('message').where({ patient_id: user.patient_id, is_read: 0 }).count();
- return this.json({ code: 0, data: { count } });
- } catch (error) {
- think.logger.error('unreadCount error:', error);
- return this.json({ code: 0, data: { count: 0 } });
- }
- }
-
- // GET /api/mp/subscribeConfig - 获取订阅消息模板配置
- async subscribeConfigAction() {
- try {
- const wechatService = this.service('wechat');
- const templates = await wechatService.getSubscribeTemplates(APP_REMARK);
- return this.json({ code: 0, data: templates });
- } catch (error) {
- think.logger.error('subscribeConfig error:', error);
- return this.json({ code: 0, data: {} });
- }
- }
-
- /**
- * 批量重新生成声明与承诺签署图并更新数据库
- * GET /api/mp/regenerateSign
- */
- async regenerateSignAction() {
- return false; // 先关闭接口,确认后再删除这行
- try {
- const patients = await this.model('patient')
- .field('id, name, sign_promise')
- .where({ is_deleted: 0, sign_promise: ['!=', ''],id: ['in', [61,62]] })
- .select();
-
- if (!patients.length) return this.json({ code: 1, msg: '没有需要处理的患者' });
-
- const doc = await this.model('content').getByKey('sign_promise');
- if (think.isEmpty(doc)) return this.json({ code: 1, msg: '协议内容未配置' });
-
- const screenshotService = this.service('screenshot');
- const results = [];
- let successCount = 0;
- let failCount = 0;
-
- for (const patient of patients) {
- try {
- const newUrl = await screenshotService.regenerate({
- originalImageUrl: patient.sign_promise,
- title: doc.title,
- content: doc.content
- });
- await this.model('patient').where({ id: patient.id }).update({ sign_promise: newUrl });
- successCount++;
- results.push({ id: patient.id, name: patient.name, status: 'ok' });
- } catch (e) {
- failCount++;
- results.push({ id: patient.id, name: patient.name, status: 'fail', error: e.message });
- think.logger.error(`regenerateSign patient ${patient.id} error:`, e);
- }
- }
-
- return this.json({
- code: 0,
- data: { total: patients.length, success: successCount, fail: failCount, results },
- msg: `处理完成:成功${successCount},失败${failCount}`
- });
- } catch (error) {
- think.logger.error('regenerateSign error:', error);
- return this.json({ code: 1, msg: '执行失败: ' + error.message });
- }
- }
-
- /**
- * POST /api/mp/regenerateSignByUrl
- * Temp no-login endpoint. Rebuild sign_income/sign_privacy/sign_promise with a fresh signature image.
- * Body: { id, url }
- */
- async regenerateSignByUrlAction() {
- return false; // 先关闭接口,确认后再删除这行
- const id = parseInt(this.post('id') || this.get('id'), 10);
- const signImageUrl = this.post('url') || this.post('signImageUrl') || this.get('url') || this.get('signImageUrl');
-
- if (![61, 62].includes(id)) {
- return this.json({ code: 1, msg: '只允许处理患者 61、62' });
- }
- if (!signImageUrl || !/^https?:\/\//i.test(signImageUrl)) {
- return this.json({ code: 1, msg: 'url 参数错误' });
- }
-
- try {
- const patient = await this.model('patient')
- .field('id,name,id_card,sign_income,sign_privacy,sign_promise,income_amount,create_time,update_time')
- .where({ id, is_deleted: 0 })
- .find();
-
- if (think.isEmpty(patient)) {
- return this.json({ code: 1, msg: '患者不存在' });
- }
- if (!patient.name || !patient.id_card) {
- return this.json({ code: 1, msg: '患者姓名或身份证缺失' });
- }
-
- const screenshotService = this.service('screenshot');
- const signTime = patient.update_time || patient.create_time || think.datetime(new Date());
- const tasks = [
- { field: 'sign_income', key: 'sign_income', amount: patient.income_amount },
- { field: 'sign_privacy', key: 'sign_privacy' },
- { field: 'sign_promise', key: 'sign_promise' }
- ];
- const updates = {};
- const results = [];
-
- for (const item of tasks) {
- const doc = await this.model('content').getByKey(item.key);
- if (think.isEmpty(doc)) {
- throw new Error(`${item.key} 协议内容未配置`);
- }
-
- const newUrl = await screenshotService.generate({
- title: doc.title,
- content: doc.content,
- signImageUrl,
- signerName: patient.name,
- signerIdCard: patient.id_card,
- signTime,
- amount: item.amount || null
- });
-
- updates[item.field] = newUrl;
- results.push({
- field: item.field,
- oldUrl: patient[item.field] || '',
- newUrl
- });
- }
-
- await this.model('patient').where({ id }).update(updates);
-
- return this.json({
- code: 0,
- data: { id, results },
- msg: '重生成成功'
- });
- } catch (error) {
- think.logger.error('regenerateSignByUrl error:', error);
- return this.json({ code: 1, msg: '重生成失败: ' + error.message });
- }
- }
-
- // @private
- async _verifyIdCard(name, idCard) {
- const faceidConfig = require('../config/faceid.js');
- try {
- const tencentcloud = require('tencentcloud-sdk-nodejs');
- const FaceidClient = tencentcloud.faceid.v20180301.Client;
- const client = new FaceidClient({
- credential: { secretId: faceidConfig.secretId, secretKey: faceidConfig.secretKey },
- region: faceidConfig.region,
- profile: { httpProfile: { endpoint: 'faceid.tencentcloudapi.com' } }
- });
- const result = await client.IdCardVerification({ IdCard: idCard, Name: name });
- if (result.Result === '0') return { success: true, message: '身份核验通过' };
- if (result.Result === '1') return { success: false, message: '姓名与身份证号不匹配' };
- if (result.Result === '2') return { success: false, message: '身份证号格式错误' };
- if (result.Result === '3') return { success: false, message: '姓名格式错误' };
- return { success: false, message: result.Description || '身份核验失败' };
- } catch (error) {
- think.logger.error('[FaceId] verify error:', error);
- return { success: false, message: error.message || '身份核验服务异常' };
- }
- }
- };
|