本文旨在深入探讨华为鸿蒙HarmonyOS Next系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。
在当今数字化金融时代,便捷性与安全性成为了金融支付领域的两大关键需求。随着移动支付的普及,无密码支付方式逐渐兴起,但同时也带来了更高的安全风险。在华为鸿蒙HarmonyOS Next系统中,我们可以借助Device Certificate Kit等强大的安全工具,为金融支付场景构建坚固的安全防护体系。
一、场景描述
金融支付类应用在无密码支付场景下,面临着诸多安全挑战。用户无需输入密码即可完成支付操作,这就要求系统必须能够精准识别支付请求是否来自合法用户的真实设备,防止恶意攻击者伪造设备或劫持通信进行非法支付。例如,在用户使用手机进行快捷支付时,如何确保只有用户本人授权的设备才能发起支付请求,并且在支付过程中保证交易数据的机密性、完整性和不可抵赖性,是我们需要重点解决的问题。
二、架构设计
为了实现无密码支付的安全防护,我们以Device Certificate Kit为核心,联合Crypto Architecture Kit和Universal Keystore Kit设计了一套完善的支付认证流程。
Device Certificate Kit
- 负责设备证书的全生命周期管理,包括证书的生成、存储、验证等。在设备初始化阶段,为设备生成唯一的证书,该证书将作为设备在支付场景中的身份标识。在支付过程中,通过验证设备证书来确保设备的真实性。
Crypto Architecture Kit
- 提供强大的加密算法支持,用于数据加密、签名验证等操作。在支付请求中,利用其加密算法对支付数据进行加密,确保数据在传输过程中的机密性。同时,通过对签名的验证,确保支付请求的完整性和真实性。
Universal Keystore Kit
- 用于安全地存储设备证书和相关密钥。它提供了一个安全的密钥存储环境,防止密钥被非法获取。在支付认证过程中,与Device Certificate Kit协同工作,确保证书和密钥的安全使用。
三、实现步骤
生成并管理设备证书,用于身份验证
设备在首次使用金融支付应用时,通过Universal Keystore Kit生成公私钥对。示例代码如下:
import { huks } from '@kit.UniversalKeystoreKit'; import { BusinessError } from '@kit.BasicServicesKit'; let keyAlias = 'paymentKey'; function GetGenerateProperties() { let properties: Array<huks.HuksParam> = new Array(); let index = 0; properties[index++] = { tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksKeyAlg.HUKS_ALG_ECC }; properties[index++] = { tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256 }; properties[index++] = { tag: huks.HuksTag.HUKS_TAG_PURPOSE, value: huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_SIGN | huks.HuksKeyPurpose.HUKS_KEY_PURPOSE_VERIFY }; properties[index++] = { tag: huks.HuksTag.HUKS_TAG_DIGEST, value: huks.HuksKeyDigest.HUKS_DIGEST_SHA256 } return properties; } async function GenerateKey(keyAlias: string) { let genProperties = GetGenerateProperties(); let options: huks.HuksOptions = { properties: genProperties } await huks.generateKeyItem(keyAlias, options) .then(() => { console.info(`promise: generate Key success.`); }).catch((err: BusinessError) => { console.error(`promise: generate Key failed, error: ` + err.message); }) } GenerateKey(keyAlias);
- 生成的公钥将用于生成设备证书,证书中包含设备的相关信息(如设备序列号、应用标识等),并由权威机构(或企业内部CA)进行签名。设备将证书存储在本地,同时将私钥安全地存储在Universal Keystore Kit中。
通过设备真实性证明确保请求来自真实设备
当发起支付请求时,设备首先向支付服务器发送设备证书。支付服务器使用Device Certificate Kit验证证书的有效性,包括检查证书链、证书有效期等。示例代码如下:
import { cert } from '@kit.DeviceCertificateKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { util } from '@kit.ArkTS'; // 假设这是设备证书数据(实际应用中需从设备获取) let deviceCertData = '-----BEGIN CERTIFICATE-----\n' + 'MIIBHTCBwwICA+gwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAwwPRXhhbXBsZSBSb\n' + '290IENBMB4XDTIzMDkwNTAyNDgyMloXDTI2MDUzMTAyNDgyMlowGjEYMBYGA1\n' + 'UEAwwPRXhhbXBsZSBSb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n' + 'HjG74yMIueO7z3T+dyuEIrhxTg2fqgeNB3SGfsIXlsiUfLTatUsU0i/sePnrKglj\n' + '2H8Abbx9PK0tsW/VgqwDIDAKBggqhkjOPQQDAgNJADBGAiEApVZno/Z7WyDc/mu\n' + 'RN1y57uaYMjrgnvp/AMdE8qmFiDwCIQCrIYdHVO1awaPgcdALZY+uLQi6mEs/oMJ\n' + 'LUcmaag3EQw==\n' + '-----END CERTIFICATE-----\n'; let textEncoder = new util.TextEncoder(); let encodingBlob: cert.EncodingBlob = { data: textEncoder.encodeInto(deviceCertData), encodingFormat: cert.EncodingFormat.FORMAT_PEM }; let x509Cert: cert.X509Cert = {} as cert.X509Cert; try { x509Cert = await cert.createX509Cert(encodingBlob); } catch (err) { let e: BusinessError = err as BusinessError; console.error(`createX509Cert failed, errCode:${err.code}, errMsg:${err.message}`); } // 证书链校验数据(假设,实际需根据真实情况配置) const param: cert.CertChainValidationParameters = { date: '20231212080000Z', trustAnchors: [{ CAPubKey: new Uint8Array([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, 0xbb, 0x16, 0x9d, 0x8f, 0x5c, 0x30, 0xd0, 0xba, 0x8f, 0x37, 0x6e, 0x33, 0xaf, 0x6f, 0x23, 0x71, 0x23, 0xa5, 0x49, 0x60, 0x1e, 0xd1, 0x07, 0x4b, 0xc9, 0x11, 0x7e, 0x66, 0x01, 0xba, 0x92, 0x52]), CASubject: new Uint8Array([0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x45, 0x4e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x45, 0x6e, 0x67, 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x06, 0x4c, 0x6f, 0x6e, 0x64, 0x6f, 0x6e, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x03, 0x74, 0x73, 0x31, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x03, 0x74, 0x73, 0x31, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x03, 0x74, 0x73, 0x31]) }] }; try { const validationRes = await x509Cert.validate(param); console.log('X509CertChain validate success'); } catch (err) { console.error('X509CertChain validate failed'); }
- 接着,服务器可以进一步验证证书中的设备信息,如设备序列号是否与系统中注册的一致,确保设备的真实性。
利用证书和私钥对支付请求进行签名
设备在发送支付请求前,使用存储在Universal Keystore Kit中的私钥对支付请求数据进行签名。首先获取私钥,示例代码如下:
import { huks } from '@kit.UniversalKeystoreKit'; import { BusinessError } from '@kit.BasicServicesKit'; let keyAlias = 'paymentKey'; async function getPrivateKey() { let options: huks.HuksOptions = { properties: [] } try { let key = await huks.getKeyItem(keyAlias, options); return key; } catch (err) { let e: BusinessError = err as BusinessError; console.error(`getPrivateKey failed, error: ` + err.message); } }
然后使用私钥对支付请求数据进行签名,假设支付请求数据为
paymentData
:import { cryptoFramework } from '@kit.CryptoArchitectureKit'; import { util } from '@kit.ArkTS'; let privateKey = await getPrivateKey(); async function signPaymentData(paymentData: string) { let dataToSign = new Uint8Array(textEncoder.encodeInto(paymentData).data); let signature = await cryptoFramework.sign({ key: privateKey, data: dataToSign }); console.log('Signature:', signature.data); return signature; } signPaymentData(paymentData);
- 签名后的支付请求连同设备证书一起发送给支付服务器。支付服务器收到请求后,使用Device Certificate Kit获取证书中的公钥,然后使用公钥对签名进行验证,确保支付请求在传输过程中未被篡改。
四、技术亮点
Device Certificate Kit和随机数生成器的协同应用,确保签名唯一性和不可预测性
在签名过程中,结合Crypto Architecture Kit中的安全随机数生成器生成随机数。这个随机数与支付请求数据一起进行签名,使得每次签名都具有唯一性和不可预测性。例如:
import { cryptoFramework } from '@kit.CryptoArchitectureKit'; async function generateRandomNumberForSignature() { let randomData = await cryptoFramework.generateRandomBytes(16); return randomData.data; } let randomNumber = await generateRandomNumberForSignature(); let combinedData = new Uint8Array([...randomNumber,...dataToSign]); let signature = await cryptoFramework.sign({ key: privateKey, data: combinedData });
- 这样,即使攻击者获取了部分签名信息,也无法伪造有效的签名,因为随机数的存在使得签名结果难以预测。
无密码支付场景中的防重放攻击设计
- 为了防止重放攻击,支付服务器在验证签名成功后,会记录支付请求的相关信息(如签名、时间戳等)。当收到新的支付请求时,首先检查是否为重放请求。例如,服务器可以维护一个最近收到的签名列表,在一定时间内,如果收到相同的签名,则认为是重放攻击,拒绝该请求。同时,结合时间戳的验证,确保支付请求是在合理的时间范围内发送的,避免攻击者利用过期的签名进行重放攻击。
五、示例代码
设备真实性证明核心代码(上述已部分展示,这里补充完整流程)
设备端在发起支付请求时发送证书链:
import { cert } from '@kit.DeviceCertificateKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { util } from '@kit.ArkTS'; // 假设这是设备证书链数据(实际应用中需从设备获取完整证书链) let deviceCertChainData = "-----BEGIN CERTIFICATE-----\n" + "MIID6jCCAtKgAwIBAgIIIM2q/TmRoLcwDQYJKoZIhvcNAQELBQAwWjELMAkGA1\n" + "UEBhMCRU4xEDAOBgNVBAgTB0VuZ2xhbmQxDzANBgNVBAcTBkxvbmRvbjEMMA\n" + "oGA1UEChMDdHMyMQwwCgYDVQQLEwN0czIxDDAKBgNVBAMTA3RzMjAeFw0yMzEy\n" + "MDUwNzM5MDBaFw0yNDEwMzEyMzU5MDBaMGExCzAJBgNVBAYTAkNOMRAwDgYDVQQI\n" + "EwdKaWFuZ3N1MRAwDgYDVQQHEwdOYW5qaW5nMQwwCgYDVQQKEwN0czMxDDAKBg\n" + "NVBAsTA3RzMzESMBAGA1UEAxMJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" + "MIIBCgKCAQEAtt+2QxUevbolYLp51QGcUpageI4fwGLIqv4fj4aoVnHFOOBqVOVpfCLR\n" + "p26LFV/F8ebwPyo8YEBKSwXzMD1573rMSbaH9BalscH5lZYAbetXoio6YRvzlcmc\n" + "rVvLBNMeVnxY86xHpo0MTNyP7W024rZsxWO98xFQVdoiaBC+7+midlisx2Y+7u0\n" + "zT9GjeUP6JLdLFUZJKUPSTK3jVzw9v1eZQZKYoNfU6vFMd6ndtwW6qEnwpzmmX\n" + "/UT+p5ThAMH593zszlz330nTSXBjIsGkyvOz9gSB0Z0LAuJj06XUNhGL5xKJYKbdI3\n" + "8MFQFJKvRHfgTAvVsvAvpBUM2DuBKwIDAQABo4GsMIGpMAkGA1UdEwQCMAA\n" + "wHQYDVR0OBBYEFDfsHTMZwoA6eaDFlBUyDpka+sYtMAsGA1UdDwQEAwID+DAnBgN\n" + "VHSUEIDAeBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMEMBQGA1UdEQQNM\n" + "AuCCTEyNy4wLjAuMTARBglghkgBhvhCAQEEBAMCBkAwHgYJYIZIAYb4QgENBBEWD3hj\n" + "YSBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAQEAp5vTvXrt8ZpgRJVtzv9ss0lJ\n" + "izp1fJf+ft5cDXrs7TSD5oHrSW2vk/ZieIMhexU4LFwhs4OE7jK6pgI48Dseqxx7\n" + "B/KktxhVMJUmVXd9Ayjp6f+BtZlIk0cArPuoXToXjsV8caTGBXHRdzxpAk/w9syc\n" + "GYrbH9TrdNMuTizOb+k268oKXUageZNxHmd7YvOXkcNgrd29jzwXKDYYiUa1DI\n" + "SzDnYaJOgPt0B/5izhoWNK7GhJDy9KEuLURcTSWFysbbnljwO9INPT9MmlS83PdAg\n" + "NiS8VXF4pce1W9U5jH7d7k0JDVSXybebe1iPFphsZpYM/NE+jap+mPy1nTCbf9g==\n" + "-----END CERTIFICATE-----\n" + "-----BEGIN CERTIFICATE-----\n" + "MIIC0zCCAoWgAwIBAgIIXpLoPpQVWnkwBQYDK2VwMFoxCzAJBgNVBAYTAkV\n" + "OMRAwDgYDVQQIEwdFbmdsYW5kMQ8wDQYDVQQHEwZMb25kb24xDDAKBgNVBAoT\n" + "A3RzMTEMMAoGA1UECxMDdHMxMQwwCgYDVQQDEwN0czEwHhcNMjMxMjA1MDczNzA\n" + "wWhcNMjQwOTAxMjM1OTAwWjBaMQswCQYDVQQGEwJFTjEQMA4GA1UECBMHRW5nbGFu\n" + "ZDEPMA0GA1UEBxMGTG9uZG9uMQwwCgYDVQQKEwN0czIxDDAKBgNVBAsTA3RzMjEMM\n" + "AoGA1UEAxMDdHMyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtt+2QxUev\n" + "bolYLp51QGcUpageI4fwGLIqv4fj4aoVnHFOOBqVOVpfCLRp26LFV/F8ebwPyo8YEBK\n" + "SwXzMD1573rMSbaH9BalscH5lZYAbetXoio6YRvzlcmcrVvLBNMeVnxY86xHpo0\n" + "MNTyP7W024rZsxWO98xFQVdoiaBC+7+midlisx2Y+7u0jzT9GjeUP6JLdLFUZJKUP\n" + "STK3jVzw9v1eZQZKYoNfU6vFMd6ndtwW6qEnwpzmmX/UT+p5ThAMH593zszlz\n" + "330nTSXBjIsGkyvOz9gSB0Z0LAuJj06XUNhGL5xKJYKbdI38MFQFJKvRHfgTAvVsvAv\n" + "pBUM2DuBKwIDAQABo28wbTAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBQ3\n" + "7B0zGcKAOnmgxZQVMg6ZGvrGLTALBgNVHQ8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAg\n" + "AHMB4GCGCGSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwBQYDK2VwA0EAuasLBe\n" + "55YgvFb4wmHeohylc9r8cFGS1LNQ5UcSn3cGqMYf6ehnef16NLuCW6upHCs8Sui4iAMvs\n" + "uKPWR9dKBA==\n" + "-----END CERTIFICATE-----\n"; let textEncoder = new util.TextEncoder(); let encodingBlob: cert.EncodingBlob = { data: textEncoder.encodeInto(deviceCertChainData), encodingFormat: cert.EncodingFormat.FORMAT_PEM }; let x509CertChain: cert.X509CertChain = {} as cert.X509CertChain; try { x509CertChain = await cert.createX509CertChain(encodingBlob); } catch (err) { let e: BusinessError = err as BusinessError; console.error(`createX509CertChain failed, errCode: ${e.code}, errMsg: ${e.message}`); } // 发送证书链到服务器(这里只是示例,实际通信方式需根据具体情况实现) sendCertChainToServer(x509CertChain);
服务器端验证证书链:
import { cert } from '@kit.DeviceCertificateKit'; import { BusinessError } from '@kit.BasicServicesKit'; // 接收到设备发送的证书链后进行验证 async function verifyDeviceCertChain(certChain: cert.X509CertChain) { // 证书链校验数据(假设,实际需根据真实情况配置) const param: cert.CertChainValidationParameters = { date: '20231212080000Z', trustAnchors: [{ CAPubKey: new Uint8Array([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, 0xbb, 0x16, 0x9d, 0x8f, 0x5c, 0x30, 0xd0, 0xba, 0x8f, 0x37, 0x6e, 0x33, 0xaf, 0x6f, 0x23, 0x71, 0x23, 0xa5, 0x49, 0x60, 0x1e, 0xd1, 0x07, 0x4b, 0xc9, 0x11, 0x7e, 0x66, 0x01, 0xba, 0x92, 0x52]), CASubject: new Uint8Array([0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x45, 0x4e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07, 0x45, 0x6e, 0x67, 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x06, 0x4c, 0x6f, 0x6e, 0x64, 0x6f, 0x6e, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x03, 0x74, 0x73, 0x31, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x03, 0x74, 0x73, 0x31, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x03, 0x74, 0x73, 0x31]) }] }; try { const validationRes = await certChain.validate(param); console.log('X509CertChain validate success'); return true; } catch (err) { console.error('X509CertChain validate failed'); return false; } }
签名生成与校验核心代码
设备端签名生成(上述已部分展示,补充完整):
import { huks } from '@kit.UniversalKeystoreKit'; import { cryptoFramework } from '@kit.CryptoArchitectureKit'; import { util } from '@kit.ArkTS'; import { BusinessError } from '@kit.BasicServicesKit'; let keyAlias = 'paymentKey'; async function getPrivateKey() { let options: huks.HuksOptions = { properties: [] } try { let key = await huks.getKeyItem(keyAlias, options); return key; } catch (err) { let e: BusinessError = err as BusinessError; console.error(`getPrivateKey failed, error: ` + err.message); } } let privateKey = await getPrivateKey(); async function signPaymentData(paymentData: string) { let dataToSign = new Uint8Array(textEncoder.encodeInto(paymentData).data); let signature = await cryptoFramework.sign({ key: privateKey, data: dataToSign }); console.log('Signature:', signature.data); return signature; } // 假设这是支付请求数据 let paymentData = '{"amount":100,"merchant":"example","orderId":"123456"}'; let signature = await signPaymentData(paymentData); // 将签名和支付数据一起发送到服务器(这里只是示例,实际通信方式需根据具体情况实现) sendSignatureAndDataToServer(signature, paymentData);
服务器端签名校验:
import { cert } from '@kit.DeviceCertificateKit'; import { cryptoFramework } from '@kit.CryptoArchitectureKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { util } from '@kit.ArkTS'; // 接收到设备发送的签名和支付数据后进行校验 async function verifySignature(signature: Uint8Array, paymentData: string, deviceCert: cert.X509Cert) { try { // 获取证书中的公钥 let pubKey = deviceCert.getPublicKey(); let dataToVerify = new Uint8Array(textEncoder.encodeInto(paymentData).data); let result = await cryptoFramework.verify({ key: pubKey, data: dataToVerify, signature: signature }); if (result) { console.log('Signature verification succeeded.'); return true; } else { console.log('Signature verification failed.'); return false; } } catch (error) { let e: BusinessError = error as BusinessError; console.error(`verifySignature failed, errCode: ${e.code}, errMsg:${e.message}`); return false; } }
支付请求验证核心代码(包含防重放攻击检查)
服务器端收到支付请求后进行综合验证:
import { cert } from '@kit.DeviceCertificateKit'; import { cryptoFramework } from '@kit.CryptoArchitectureKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { util } from '@kit.ArkTS'; // 假设这是存储已验证签名和相关信息的列表(实际应用中需使用合适的数据结构,如数据库表或缓存) let verifiedSignatures: { signature: Uint8Array, timestamp: number }[] = []; async function processPaymentRequest(signature: Uint8Array, paymentData: string, deviceCertChain: cert.X509CertChain) { // 验证设备证书链 let isCertChainValid = await verifyDeviceCertChain(deviceCertChain); if (!isCertChainValid) { console.log('Device certificate chain verification failed.'); return false; } // 获取设备证书 let deviceCert = deviceCertChain.getCertList()[0]; // 验证签名 let isSignatureValid = await verifySignature(signature, paymentData, deviceCert); if (!isSignatureValid) { console.log('Signature verification failed.'); return false; } // 检查是否为重放攻击 let currentTimestamp = Date.now(); if (isReplayAttack(signature, currentTimestamp)) { console.log('Replay attack detected.'); return false; } // 将签名和时间戳添加到已验证列表 verifiedSignatures.push({ signature, timestamp: currentTimestamp }); console.log('Payment request verification succeeded.'); return true; } function isReplayAttack(signature: Uint8Array, currentTimestamp: number): boolean { // 简单的重放攻击检查逻辑,这里假设在过去1分钟内相同签名视为重放攻击 const replayWindow = 60 * 1000; for (let i = 0; i < verifiedSignatures.length; i++) { if (verifiedSignatures[i].signature === signature && currentTimestamp - verifiedSignatures[i].timestamp < replayWindow) { return true; } } return false; }
通过以上基于Device Certificate Kit的无密码支付认证设计,我们为金融支付场景构建了一个全面而强大的安全防护体系。在实际应用中,我们可以根据具体业务需求和安全标准,进一步优化和完善该体系,确保金融支付的安全与便捷。希望这篇文章能为从事鸿蒙金融支付应用开发的小伙伴们提供一些有益的参考和启发。如果在实现过程中遇到问题,不要着急,仔细分析,参考官方相关文档和示例代码,相信一定能够找到解决方案。加油!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。