杰瑞科技汇

Oracle数据库应用教程如何快速入门?

Oracle数据库应用教程:从入门到实践

目录

  1. 第一部分:Oracle基础入门

    Oracle数据库应用教程如何快速入门?-图1
    (图片来源网络,侵删)
    • 1 什么是Oracle数据库?
    • 2 Oracle数据库的特点与优势
    • 3 Oracle数据库体系结构简介
    • 4 如何安装和配置Oracle数据库
    • 5 连接到Oracle数据库
  2. 第二部分:SQL语言核心

    • 1 SQL简介与分类
    • 2 数据定义语言
    • 3 数据操纵语言
    • 4 数据查询语言 - SELECT
    • 5 事务控制语言
    • 6 数据控制语言
  3. 第三部分:数据库对象管理

    • 1 表
    • 2 约束
    • 3 索引
    • 4 视图
    • 5 序列
    • 6 同义词
  4. 第四部分:PL/SQL编程

    • 1 什么是PL/SQL?
    • 2 PL/SQL块结构
    • 3 变量、常量与数据类型
    • 4 控制结构
    • 5 游标
    • 6 异常处理
    • 7 存储过程与函数
  5. 第五部分:高级应用与性能优化

    Oracle数据库应用教程如何快速入门?-图2
    (图片来源网络,侵删)
    • 1 数据库设计范式
    • 2 分区表
    • 3 索引类型与优化
    • 4 执行计划分析
    • 5 SQL性能优化原则
  6. 第六部分:实战案例与总结

    • 1 案例背景:设计一个简单的电商系统
    • 2 数据库设计
    • 3 建表与约束
    • 4 编写PL/SQL业务逻辑
    • 5 总结与学习路径

第一部分:Oracle基础入门

1 什么是Oracle数据库?

Oracle数据库是由Oracle Corporation(甲骨文公司)开发的一款关系型数据库管理系统,它是一个功能强大、稳定可靠、安全性极高的商业数据库产品,广泛应用于金融、电信、政府、大型企业等关键业务领域。

2 Oracle数据库的特点与优势

  • 可靠性高:提供强大的数据恢复、备份和容错能力,确保数据不丢失。
  • 性能卓越:通过先进的优化器、并行查询和内存管理技术,处理海量数据和高并发请求。
  • 安全性强:提供精细化的权限控制、数据加密和审计功能。
  • 可扩展性好:支持从小型应用到超大规模数据仓库的平滑扩展。
  • 功能全面:除了核心的RDBMS功能,还集成了应用服务器、开发工具套件等。

3 Oracle数据库体系结构简介

理解Oracle体系结构是学好Oracle的关键,主要组件包括:

  • 实例:一组后台进程和内存区域的集合,负责管理数据库,当数据库启动时,实例随之启动。
    • 系统全局区:一个共享的内存区域,存放数据库数据和控制信息。
    • 进程:负责处理请求的后台进程(如PMON, SMON)和用户进程。
  • 数据库:物理存储数据的文件集合。
    • 数据文件:存储实际的数据和元数据。
    • 控制文件:记录数据库的物理结构信息,是数据库启动的“钥匙”。
    • 重做日志文件:记录所有对数据库的修改,用于实例恢复和介质恢复。
    • 参数文件:配置实例的启动参数。

简单比喻:实例就像“公司的大脑和员工”,负责思考和干活;数据库就像“公司的仓库和档案柜”,负责存储资料,两者缺一不可。

Oracle数据库应用教程如何快速入门?-图3
(图片来源网络,侵删)

4 如何安装和配置Oracle数据库

对于初学者,推荐使用 Oracle Database Express Edition (XE),它是免费的,功能精简但足以满足学习和开发需求。

  1. 下载:从Oracle官网下载适用于你操作系统的Oracle XE安装包。
  2. 安装:运行安装程序,按照向导完成安装,记住设置的 SYS/SYSTEM 用户密码。
  3. 配置:安装完成后,会自动创建一个名为 XE 的服务,可以通过 http://localhost:8080/apex 访问Oracle Application Express (APEX),这是一个基于Web的数据库管理工具。

5 连接到Oracle数据库

有多种方式连接:

  • *SQLPlus**:Oracle自带的命令行工具。
    • 在Windows命令行或Linux终端输入:sqlplus username/password@hostname:port/service_name
    • 连接到本地XE:sqlplus system/your_password@localhost:1521/XE
  • SQL Developer:Oracle免费提供的图形化客户端工具,功能强大,推荐使用。

    下载并安装,新建数据库连接,填写相应信息即可。

  • 第三方工具:如DBeaver, Navicat等也支持Oracle。

第二部分:SQL语言核心

SQL是操作关系型数据库的标准语言,Oracle的SQL在标准SQL基础上进行了扩展。

1 SQL简介与分类

  • DDL (Data Definition Language):定义数据库结构。
    • CREATE, ALTER, DROP, TRUNCATE, RENAME
  • DML (Data Manipulation Language):操作数据库数据。
    • INSERT, UPDATE, DELETE, MERGE
  • DQL (Data Query Language):查询数据。
    • SELECT (虽然常被归为DML,但功能独立)
  • TCL (Transaction Control Language):控制事务。
    • COMMIT, ROLLBACK, SAVEPOINT
  • DCL (Data Control Language):控制权限。
    • GRANT, REVOKE

2 数据定义语言

-- 创建一个表
CREATE TABLE employees (
    employee_id   NUMBER(6) PRIMARY KEY,
    first_name    VARCHAR2(20),
    last_name     VARCHAR2(25),
    email         VARCHAR2(25) UNIQUE,
    hire_date     DATE,
    salary        NUMBER(8, 2),
    department_id NUMBER(4)
);
-- 修改表结构 (增加一列)
ALTER TABLE employees ADD (job_id VARCHAR2(10));
-- 删除表
DROP TABLE employees;

3 数据操纵语言

-- 插入数据
INSERT INTO employees (employee_id, first_name, last_name, email, hire_date, salary, department_id)
VALUES (101, 'John', 'Doe', 'john.doe@example.com', TO_DATE('2025-01-15', 'YYYY-MM-DD'), 5000.00, 10);
-- 更新数据
UPDATE employees SET salary = 5500.00 WHERE employee_id = 101;
-- 删除数据
DELETE FROM employees WHERE employee_id = 101;

4 数据查询语言 - SELECT (核心中的核心)

SELECT 语句是功能最丰富的部分,语法结构如下:

SELECT column_list | *
FROM table_name
[WHERE condition]
[GROUP BY column_list]
[HAVING group_condition]
[ORDER BY column_list [ASC | DESC]];

示例:

-- 查询所有员工信息
SELECT * FROM employees;
-- 查询特定列
SELECT first_name, last_name, salary FROM employees;
-- 带条件的查询
SELECT * FROM employees WHERE salary > 5000 AND department_id = 10;
-- 排序
SELECT first_name, salary FROM employees ORDER BY salary DESC;
-- 分组统计
SELECT department_id, AVG(salary), COUNT(*)
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 4500;

5 事务控制语言

事务是一个逻辑工作单元,要么全部成功,要么全部失败。

-- 开始一个事务 (DML操作自动开始一个隐式事务)
INSERT INTO ...
UPDATE ...
-- 提交事务,将修改永久保存到数据库
COMMIT;
-- 回滚事务,撤销自上次提交以来的所有修改
ROLLBACK;

6 数据控制语言

-- 授予用户scott查询employees表的权限
GRANT SELECT ON employees TO scott;
-- 撤销权限
REVOKE SELECT ON employees FROM scott;

第三部分:数据库对象管理

1 表

表是数据库中存储数据的对象,由行和列组成。

2 约束

约束用于保证数据的完整性和一致性。

  • PRIMARY KEY:主键,唯一标识表中的每一行。
  • FOREIGN KEY:外键,建立两个表之间的引用关系。
  • UNIQUE:唯一约束,确保列中的值是唯一的。
  • NOT NULL:非空约束,确保列不能有NULL值。
  • CHECK:检查约束,确保列中的值满足特定条件。
CREATE TABLE departments (
    department_id NUMBER(4) PRIMARY KEY,
    department_name VARCHAR2(30) NOT NULL
);
CREATE TABLE employees (
    employee_id NUMBER(6) PRIMARY KEY,
    first_name VARCHAR2(20),
    department_id NUMBER(4),
    CONSTRAINT fk_dept FOREIGN KEY (department_id) REFERENCES departments(department_id)
);

3 索引

索引是用于提高查询性能的数据库对象,类似于书的目录,它创建在表的一列或多列上。

-- 在last_name列上创建索引
CREATE INDEX idx_employees_lastname ON employees(last_name);

注意:索引能加速查询,但会降低INSERT, UPDATE, DELETE的速度,并占用存储空间,只为经常用于查询条件的列创建索引。

4 视图

视图是一个虚拟表,其内容由查询定义,它不存储数据,而是动态地从基表中生成。

  • 优点:简化复杂查询、隐藏数据细节、提供数据安全层。
    -- 创建一个只显示员工姓名和部门名称的视图
    CREATE VIEW v_employee_dept AS
    SELECT e.first_name, e.last_name, d.department_name
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id;

-- 像使用普通表一样使用视图 SELECT * FROM v_employee_dept WHERE department_name = 'IT';


#### 3.5 序列
序列用于生成唯一的数字序列,常用于主键。
```sql
-- 创建一个序列
CREATE SEQUENCE seq_employee_id
START WITH 1
INCREMENT BY 1
NOCACHE;
-- 使用序列
INSERT INTO employees (employee_id, first_name)
VALUES (seq_employee_id.NEXTVAL, 'Jane');

6 同义词

同义词是数据库对象(如表、视图、序列)的别名,用于简化访问或隐藏对象所有者。

-- 为scott.employees表创建一个公共同义词
CREATE PUBLIC SYNONYM emp FOR scott.employees;
-- 现在可以直接使用 emp 来访问 scott.employees 表

第四部分:PL/SQL编程

PL/SQL是Oracle对SQL的过程化扩展,它将SQL的数据操作能力和高级语言的过程化能力结合起来。

1 什么是PL/SQL?

PL/SQL允许在数据库服务器端编写存储过程、函数、触发器等程序单元,实现复杂的业务逻辑,减少网络传输,提高应用性能。

2 PL/SQL块结构

PL/SQL程序由逻辑块组成,每个块由三部分组成:

DECLARE
    -- 声明部分:定义变量、常量、游标等
    v_employee_name VARCHAR2(50);
BEGIN
    -- 执行部分:包含SQL语句和PL/SQL逻辑
    SELECT first_name INTO v_employee_name FROM employees WHERE employee_id = 101;
    DBMS_OUTPUT.PUT_LINE('Employee Name: ' || v_employee_name);
    -- 异常处理部分(可选)
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('Employee not found.');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('An error occurred: ' || SQLERRM);
END;
/

注意:在SQL*Plus中,执行PL/SQL块需要以

3 变量、常量与数据类型

DECLARE
    v_salary NUMBER(8, 2) := 0;
    c_bonus_rate CONSTANT NUMBER(3, 2) := 0.1; -- 常量
    v_bonus employees.salary%TYPE; -- %TYPE: 基于表中列的数据类型
    v_rec employees%ROWTYPE;      -- %ROWTYPE: 基于表中整行的数据类型
BEGIN
    -- 赋值
    SELECT salary INTO v_salary FROM employees WHERE employee_id = 101;
    v_bonus := v_salary * c_bonus_rate;
    -- 使用%ROWTYPE
    SELECT * INTO v_rec FROM employees WHERE employee_id = 101;
    DBMS_OUTPUT.PUT_LINE('Full Name: ' || v_rec.first_name || ' ' || v_rec.last_name);
END;
/

4 控制结构

-- IF 语句
IF v_salary > 10000 THEN
    DBMS_OUTPUT.PUT_LINE('High earner.');
ELSIF v_salary > 5000 THEN
    DBMS_OUTPUT.PUT_LINE('Medium earner.');
ELSE
    DBMS_OUTPUT.PUT_LINE('Standard earner.');
END IF;
-- LOOP 循环
FOR i IN 1..10 LOOP
    DBMS_OUTPUT.PUT_LINE(i);
END LOOP;
-- WHILE 循环
WHILE v_count < 10 LOOP
    v_count := v_count + 1;
END LOOP;

5 游标

游标用于处理查询返回的多行结果,它像一个指针,可以逐行遍历结果集。

DECLARE
    CURSOR c_employees IS SELECT first_name, salary FROM employees;
    v_first_name employees.first_name%TYPE;
    v_salary employees.salary%TYPE;
BEGIN
    OPEN c_employees; -- 打开游标
    LOOP
        FETCH c_employees INTO v_first_name, v_salary; -- 获取一行
        EXIT WHEN c_employees%NOTFOUND; -- 当没有更多数据时退出
        DBMS_OUTPUT.PUT_LINE('Name: ' || v_first_name || ', Salary: ' || v_salary);
    END LOOP;
    CLOSE c_employees; -- 关闭游标
END;
/

隐式游标:对于单行SELECT ... INTO语句,Oracle会自动创建和管理一个隐式游标。

BEGIN
    SELECT first_name INTO v_first_name FROM employees WHERE employee_id = 101;
    -- 可以通过隐式游标属性检查,如 SQL%FOUND, SQL%NOTFOUND
END;
/

6 异常处理

异常处理是PL/SQL健壮性的关键。

  • 预定义异常:如 NO_DATA_FOUND, TOO_MANY_ROWS, ZERO_DIVIDE

  • 自定义异常:由用户定义。

    DECLARE
    v_salary employees.salary%TYPE;
    e_salary_too_high EXCEPTION; -- 自定义异常
    BEGIN
    SELECT salary INTO v_salary FROM employees WHERE employee_id = 101;
    IF v_salary > 20000 THEN
        RAISE e_salary_too_high; -- 手动抛出异常
    END IF;

EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('Error: Employee not found.'); WHEN e_salary_too_high THEN DBMS_OUTPUT.PUT_LINE('Error: Salary is too high!'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Unexpected Error: ' || SQLERRM); END; /


#### 4.7 存储过程与函数
将PL/SQL代码块存储在数据库中,以便重复调用。
**存储过程**
```sql
CREATE OR REPLACE PROCEDURE p_increase_salary (
    p_emp_id IN NUMBER,
    p_percentage IN NUMBER
)
IS
BEGIN
    UPDATE employees
    SET salary = salary * (1 + p_percentage / 100)
    WHERE employee_id = p_emp_id;
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('Salary updated for employee ' || p_emp_id);
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('Failed to update salary: ' || SQLERRM);
END p_increase_salary;
/

调用过程

EXEC p_increase_salary(101, 10); -- 给员工ID为101的员工加薪10%

函数 函数与过程类似,但必须返回一个值。

CREATE OR REPLACE FUNCTION f_get_employee_name (
    p_emp_id IN NUMBER
) RETURN VARCHAR2
IS
    v_full_name VARCHAR2(50);
BEGIN
    SELECT first_name || ' ' || last_name INTO v_full_name
    FROM employees
    WHERE employee_id = p_emp_id;
    RETURN v_full_name;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN 'Unknown';
END f_get_employee_name;
/

调用函数

DECLARE
    v_name VARCHAR2(50);
BEGIN
    v_name := f_get_employee_name(101);
    DBMS_OUTPUT.PUT_LINE('Employee Name: ' || v_name);
END;
/

第五部分:高级应用与性能优化

1 数据库设计范式

  • 第一范式 (1NF):确保列的原子性,即每个单元格的值不可再分。
  • 第二范式 (2NF):在1NF基础上,非主键列必须完全依赖于整个主键(消除部分依赖)。
  • 第三范式 (3NF):在2NF基础上,非主键列之间不能有传递依赖(消除传递依赖)。 注意:范式化可以减少数据冗余,但可能导致过多关联查询,在实际应用中,需要在范式化和反范式化之间权衡。

2 分区表

当表非常大时(例如数千万行),可以将表按某种规则(如时间、范围、列表)分割成更小的、更易于管理的物理片段,即分区。

  • 优点:提高查询性能(只需扫描特定分区)、简化数据管理(可单独备份/恢复分区)。
    -- 创建一个按范围分区的表
    CREATE TABLE sales (
    sale_id      NUMBER,
    sale_date    DATE,
    amount       NUMBER
    )
    PARTITION BY RANGE (sale_date) (
    PARTITION sales_q1_2025 VALUES LESS THAN (TO_DATE('01-APR-2025', 'DD-MON-YYYY')),
    PARTITION sales_q2_2025 VALUES LESS THAN (TO_DATE('01-JUL-2025', 'DD-MON-YYYY')),
    PARTITION sales_q3_2025 VALUES LESS THAN (TO_DATE('01-OCT-2025', 'DD-MON-YYYY')),
    PARTITION sales_q4_2025 VALUES LESS THAN (TO_DATE('01-JAN-2025', 'DD-MON-YYYY'))
    );

3 索引类型与优化

  • B-Tree索引:最常用的索引类型,默认选择。
  • 位图索引:适合低基数列(如性别、状态),在数据仓库中常用。
  • 函数索引:基于列的函数或表达式创建索引,用于优化带函数的查询。
    -- 创建函数索引
    CREATE INDEX idx_upper_lastname ON employees(UPPER(last_name));

4 执行计划分析

执行计划是Oracle数据库执行查询的详细步骤,分析它是优化SQL的核心。

-- 在SQL*Plus中查看执行计划
EXPLAIN PLAN FOR
SELECT * FROM employees WHERE last_name = 'King';
-- 查看执行计划
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

关注点:

  • TABLE ACCESS FULL:全表扫描,通常很慢,应尽量避免。
  • INDEX RANGE SCAN:索引范围扫描,比全表扫描快。
  • JOIN 方法:Nested Loops, Hash Join, Sort Merge Join。

5 SQL性能优化原则

  1. 减少访问数据量:只查询需要的列 (SELECT column1, column2 ... 而不是 SELECT *)。
  2. 利用索引:确保WHERE子句中的条件能使用上索引。
  3. 避免在索引列上使用函数:如 WHERE UPPER(last_name) = 'KING' 会使索引失效,除非使用函数索引。
  4. 避免在索引列上计算:如 WHERE salary * 1.1 > 5000 会使索引失效。
  5. 合理使用JOIN:根据数据量选择合适的JOIN算法。
  6. 使用绑定变量:避免SQL硬编码,使用占位符,可以共享游标,提高性能。

第六部分:实战案例与总结

1 案例背景:设计一个简单的电商系统

我们需要管理用户、商品、订单和订单详情。

2 数据库设计

  • 用户表users (user_id, username, password, email, ...)
  • 商品表products (product_id, product_name, price, stock_quantity, ...)
  • 订单表orders (order_id, user_id, order_date, total_amount, status, ...)
  • 订单详情表order_items (item_id, order_id, product_id, quantity, unit_price, ...)

3 建表与约束

-- 用户表
CREATE TABLE users (
    user_id    NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    username   VARCHAR2(50) UNIQUE NOT NULL,
    password   VARCHAR2(100) NOT NULL,
    email      VARCHAR2(100) UNIQUE NOT NULL
);
-- 商品表
CREATE TABLE products (
    product_id   NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    product_name VARCHAR2(100) NOT NULL,
    description  CLOB,
    price        NUMBER(10, 2) NOT NULL CHECK (price > 0),
    stock        NUMBER(10) NOT NULL CHECK (stock >= 0)
);
-- 订单表
CREATE TABLE orders (
    order_id    NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    user_id     NUMBER NOT NULL,
    order_date  TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
    total_amount NUMBER(10, 2) NOT NULL,
    status      VARCHAR2(20) DEFAULT 'PENDING' NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);
-- 订单详情表
CREATE TABLE order_items (
    item_id      NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    order_id     NUMBER NOT NULL,
    product_id   NUMBER NOT NULL,
    quantity     NUMBER(5) NOT NULL CHECK (quantity > 0),
    unit_price   NUMBER(10, 2) NOT NULL,
    FOREIGN KEY (order_id) REFERENCES orders(order_id),
    FOREIGN KEY (product_id) REFERENCES products(product_id)
);

4 编写PL/SQL业务逻辑

创建一个存储过程来处理下单逻辑。

CREATE OR REPLACE PROCEDURE p_place_order (
    p_user_id     IN NUMBER,
    p_items_list  IN SYS.ODCIVARCHAR2LIST, -- 假设传入一个产品ID和数量的列表,格式如 '1,2; 3,1'
    p_order_id    OUT NUMBER
)
IS
    v_total_amount NUMBER(10, 2) := 0;
    v_item_count   NUMBER := 0;
    v_product_id   NUMBER;
    v_quantity     NUMBER;
    CURSOR c_items IS
        SELECT REGEXP_SUBSTR(p_items_list, '[^;]+', 1, LEVEL) AS item_pair
        FROM dual
        CONNECT BY LEVEL <= REGEXP_COUNT(p_items_list, ';') + 1;
BEGIN
    -- 1. 计算总金额并检查库存
    FOR r_item IN c_items LOOP
        -- 解析出 product_id 和 quantity
        -- (这里简化了复杂的字符串解析逻辑,实际应用中应使用更健壮的方法)
        -- v_product_id := ...;
        -- v_quantity := ...;
        -- 检查库存
        -- SELECT stock INTO v_stock FROM products WHERE product_id = v_product_id;
        -- IF v_stock < v_quantity THEN
        --    RAISE_APPLICATION_ERROR(-20001, 'Insufficient stock for product ' || v_product_id);
        -- END IF;
        -- 计算小计
        -- v_total_amount := v_total_amount + (SELECT price FROM products WHERE product_id = v_product_id) * v_quantity;
        -- v_item_count := v_item_count + 1;
    END LOOP;
    IF v_item_count = 0 THEN
        RAISE_APPLICATION_ERROR(-20002, 'Order cannot be empty.');
    END IF;
    -- 2. 创建订单
    INSERT INTO orders (user_id, total_amount)
    VALUES (p_user_id, v_total_amount)
    RETURNING order_id INTO p_order_id;
    -- 3. 创建订单详情并扣减库存
    -- FOR r_item IN c_items LOOP
    --    INSERT INTO order_items (order_id, product_id, quantity, unit_price)
    --    VALUES (p_order_id, v_product_id, v_quantity, (SELECT price FROM products WHERE product_id = v_product_id));
    --
    --    UPDATE products SET stock = stock - v_quantity WHERE product_id = v_product_id;
    -- END LOOP;
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('Order placed successfully. Order ID: ' || p_order_id);
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('Failed to place order: ' || SQLERRM);
        RAISE;
END p_place_order;
/

注意:此处的下单逻辑是一个简化示例,实际下单是分布式事务,需要考虑并发、库存锁定等复杂问题。

5 总结与学习路径

本教程为您勾勒了Oracle数据库应用的完整知识体系,学习是一个循序渐进的过程:

  1. 打好基础:熟练掌握SQL和DDL/DML/TCL。
  2. 深入PL/SQL:掌握变量、控制结构、游标、异常处理,这是写出高效数据库逻辑的关键。
  3. 理解体系结构:了解实例、数据文件、日志文件等,这有助于你理解性能问题和故障恢复。
  4. 实践与优化:多动手实践,学习分析执行计划,遵循SQL优化原则。
  5. 探索高级主题:学习RMAN备份恢复、Data Guard高可用、性能调优、集群技术等,向DBA专家迈进。

Oracle世界博大精深,希望这份教程能成为您探索之旅的坚实起点,祝您学习愉快!

分享:
扫描分享到社交APP
上一篇
下一篇