标签:str 特定 url 第一范式 获得 ast 删除用户 相关 出现
关系型数据库:SQL
非关系型数据库:NoSQL--Not Only SQL
BDMS数据库管理系统
关系型数据库管理系统 Relational Database Management System
登陆: mysql -u root -p
MySQL的注释
--注释
所有语句以分号结尾
show databases;
--查看所有数据库
use school;
--切换数据库
show tables;
--查看数据库中所有的表
describe student;
--查看student表的所有信息
create database westos;
--创建数据库
exit;
--退出连接
DDL 定义
DML操作
DQL查询
DCL控制
创建数据库
CREATE DATABASE name;
CREATE DATABASE IF NOT EXISTS name;
先判断是否存在
删除数据库
DROP DATABASE name;
DROP DATABASE IF EXISTS name;
使用数据库
USE name;
如果表名或者字段名是一个特殊字符,需要加上符号`
SElECT `user` FROM student;
数值
字符串
其中int(M)指的是最大显示位数为M,不是最大能填M位的数
比如int(1),可以存100000进去
但是VARCHAR(M)就是最大位数了
时间和日期
null
Unsigned
zerofill
auto-increment
not-null
default
拓展
/*
每一个表都存在以下五个字段
*/
id --主键
`version` --乐观锁
is_delete --伪删除
gmt_create --创建时间
gmt_update --修改时间
--表的名称和字段尽量扩起来,防止和关键字冲突
--字符串使用单引号扩起来
--所有的语句后面加逗号,最后一个可以不用加
CREATE TABLE IF NOT EXISTS `student`(
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT ‘学号‘, --COMMENT注释
`name` VARCHAR(30) NOT NULL DEFAULT ‘匿名‘ COMMENT ‘姓名‘,
`pwd` VARCHAR(20) NOT NULL DEFAULT ‘123456‘ COMMENT ‘密码‘,
`sex` VARCHAR(2) NOT NULL DEFAULT ‘女‘ COMMENT ‘性别‘,
`birthday` DATETIME DEFAULT NULL COMMENT ‘出生日期‘,
PRIMARY KEY(`id`) --主键
)ENGINE=INNODB DEFAULT CHARSET=uft8
格式:
CREATE TABLE [IF NOT EXISTS] ‘name‘(
`字段名` 列类型 [属性] [索引] [注释],
...
`字段名` 列类型 [属性] [索引] [注释]
)[表类型][字符集设置][注释]
SHOW CREATE DATABASE school
--查看创建数据库的语句
SHOW CREATE TABLE student
--查看student数据表的定义语句
DESC student
--显示表的结构
INNODB默认使用
MYISAM早些年使用的
MYISAM | INNODB | |
---|---|---|
事务支持 | 不支持 | 支持 |
数据行锁定 | 不支持 | 支持 |
外键约束 | 不支持 | 支持 |
全文索引 | 支持 | 不支持 |
表的大小 | 较小 | 较大,约为2倍 |
常规使用操作:
在物理空间存在的位置
所有的数据库文件都存在data目录下,一个文件夹对应一个数据库
本质还是文件的存储
设置数据库表的字符集编码
CHARSET=utf8
不设置的话是MySQL默认的字符集编码——Latin1(不支持中文)
修改
--修改表名
ALTER TABLE teacher RENAME AS teacher1
--增加表的字段
ALTER TABLE teacher1 ADD age INT(11)
--修改表的字段(重命名,修改约束)
ALTER TABLE teacher CHANGE age age1 INT(11)
ALTER TABLE teacher MODIFY age VARCHAR(11)
--删除表的字段
ALTER TABLE teacher DROP age1
删除
--删除表
DROP TABLE [IF EXISTS] teacher
CREATE TABLE `grade`(
`gradeid` INT(11) NOT NULL AUTO_INCREMENT,
`gradename` VARCHAR(10) NOT NULL
)ENGINE=INNODB DEFAULT CHARSET=uft8
--学生表的gardeid字段,要去引用年级表的gradeid
--定义外键key
--给这个外键添加约束(执行引用) reference 引用
CREATE TABLE IF NOT EXISTS `student`(
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT ‘学号‘, --COMMENT注释
`name` VARCHAR(30) NOT NULL DEFAULT ‘匿名‘ COMMENT ‘姓名‘,
`gradeid` INT(11) NOT NULL,
PRIMARY KEY(`id`), --主键
KEY `FK_gradeid` (`gradeid`),
CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade`(`gradeid`)
)ENGINE=INNODB DEFAULT CHARSET=uft8
ALTER TABLE `student`
ADD CONSTRAINT `FK_gradeid` FOREIGN KEY(`gradeid`) REFERENCES `grade`(`gradeid`)
--CONSTRAINT后面是约束名
以上都是物理外键,数据库级别的,数据库过多时会造成困扰,不建议使用
最佳实现
数据库意义:数据存储、数据管理
DML语言:数据操作语言
--插入语句
--INSERT INTO 表名(字段名1,字段名2,字段名3) values(‘值1,值2,值3‘)
INSERT INTO `grade`(`gradename`) VALUES(`Jonior`)
--由于主键自增我们可以省略(如果不写字段,就默认一一匹配)
--插入多组数据
INSERT INTO persons(id_p, lastname , firstName, city )
VALUES
(200,‘haha‘ , ‘deng‘ , ‘shenzhen‘),
(201,‘haha2‘ , ‘deng‘ , ‘GD‘),
(202,‘haha3‘ , ‘deng‘ , ‘Beijing‘);
--update 带有条件,不指定条件的话会改动所有值
UPDATE `student` SET `name`=‘Jack‘ WHERE id=1;
--UPDATE 表名 SET column_name = value WHERE 条件;
--修改多个属性
UPDATE `student` SET `name`=‘Jack‘,`age`=23 WHERE id=1;
--通过多个条件定位数据
UPDATE `student` SET `name`=‘JENNIE‘ WHERE `name`=‘Jack‘ AND sex=‘女‘;
--delete from 表名 [where 条件]
DELETE FROM `student` WHERE id = 1;
--完全清空一个数据库,表的结构和索引约束不会变
TRUNCATE `student`
DELETE & TURNCATE
data query language
--查询全部的字段 SELECT 字段 FROM 表名
SELECT * FROM student
--查询指定字段
SELECT `studengNo`,`studentName` FROM student
--使用别名(字段、表都可以起别名)
SELECT `studengNo` AS 学号 FROM student AS s
--函数 CONCAT拼接字符串(姓名:Lisa)
SELECT CONCAT(‘姓名:‘,studentName) AS 新名字 FROM student
--查询有哪些同学参加了考试
SELECT * FROM result --查询全部考试成绩
SELECT `studentNo` FROM result --同学No.里面有重复(一个人好几科)
SELECT DISTINCT `studentNo` FROM result --去重
数据库的列(表达式)
SELECT VERSION() --查询系统版本(函数)
SELECT 100*3-1 AS 结果 --用于计算(表达式)
SELECT @@auto_increment_increment --查询自增的步长(变量)
--学生成绩加一分查看
SELECT `studentNo`,`studentResult`+1 AS ‘提分后‘ FROM result
作用:检索数据中符合条件的值
逻辑运算符 结果为boolean值
SELECT studentNo, studentResult FROM result --查询所有成绩
SELECT studentNo, studentResult FROM result WHERE studentResult>=95 AND studentResult<=100
--模糊查询(区间)
SELECT studentNo, studentResult FROM result WHERE studentResult BETWEEN 95 AND 100 --between and 是闭区间
SELECT studentNo, studentResult FROM result WHERE NOT studentNo = 1000
模糊查询:比较运算符
运算符 | 语法 | 描述 |
---|---|---|
IS NULL | a is null | 如果为null,真 |
IS NOT NULL | a is not null | 如果不为null,真 |
BETWEEN | a between b and c | a在bc之间,真 |
LIKE | a like b | SQL匹配,a匹配b,真 |
IN | a in (a1,a2,a3,...) | a在a1a2...中某一个,真 |
--查询姓刘的同学
--LIKE+‘‘:%代表0个到多个字符、_代表一个字符
SELECT studentName FROM student WHERE studentName LIKE ‘刘%‘
--刘后面两个字的
SELECT studentName FROM student WHERE studentName LIKE ‘刘__‘
--名字当中有嘉
SELECT studentName FROM student WHERE studentName LIKE ‘%嘉%‘
--in操作:具体的一个或者多个值
SELECT studentName FROM student WHERE studentNo IN (1001,1002,1003);
JOIN
七种JOIN
![image-20210205194200770](/Users/wangyiran/Library/Application Support/typora-user-images/image-20210205194200770.png)
/*
思路:
1.分析需求,分析查询的字段来自哪些表
2.确定使用哪种连接查询?
确定交叉点(相同数据)
判断的条件:studentNo = studentNo 就连起来了
*/
--查询参加考试的同学(学号姓名科目编号分数)
SELECT s.studentNo, studentName, subjectNo, studentResult
FROM student As s
INNER JOIN result AS r
WHERE s.studentNo = r.studentNo;
--INNER JOIN 获得的是两个表格中都有的人的信息
SELECT s.studentNo, studentName, subjectNo, studentResult
FROM student As s
LEFT JOIN result AS r
ON s.studentNo = r.studentNo;
--LEFT JOIN 获得的是student表格中全部的信息,s有r没有的信息补NULL
SELECT s.studentNo, studentName, subjectNo, studentResult
FROM student As s
RIGHT JOIN result AS r
ON s.studentNo = r.studentNo;
--RIGHT JOIN 获得的是result表格中全部的信息,r有s没有的信息补NULL
--eg:找到缺考的人(即没有成绩)
SELECT s.studentNo, studentName, subjectNo, studentResult
FROM student As s
LEFT JOIN result AS r
ON s.studentNo = r.studentNo
WHERE studentResult IS NULL;
使用ON和WHERE的区别?
其实以上结果的关键原因就是left join,right join,full join的特殊性,不管on上的条件是否为真都会返回left或right表中的记录,full则具有left和right的特性的并集。 而inner join没这个特殊性,则条件放在on中和where中,返回的结果集是相同的。on为了反映外连接中一方的全连接,而where没有这个功能,内连接配对是可以的。
--三个表连接
--student、result、subject
SELECT s.studentNo, `studentName`, `subjectName`, `studentResult`
FROM student s
RIGHT JOIN result r
ON s.studentNo = r.studentNo
INNER JOIN subject sub
ON r.subjectNo = sub.subjectNo
自己的表和自己连接,即:一张表拆为两张一样的表
SELECT a.categoryName AS ‘parent‘, b.categoryName AS ‘son‘
FROM category AS a, category AS b
WHERE a.categoryid = b.pid
limit & order by
排序
--order by:升序ASC、降序DESC
--根据JAVA成绩的降序排序
SELECT s.studentNo, studentName, subjectName, studentResult
FROM student s
INNER JOIN result r
ON s.studentNo = r.studentNo
INNER JOIN subject sub
ON r.subjectNo = sub.subjectNo
WHERE subjectName = ‘JAVA‘
ORDER BY studentResult DESC
分页
缓解数据库压力、给人的体验更好
--每页只显示五条数据
--limit 起始值,页面大小
--LIMIT (N-1)*5, PageSize. N:当前页数
SELECT s.studentNo, studentName, subjectName, studentResult
FROM student s
INNER JOIN result r
ON s.studentNo = r.studentNo
INNER JOIN subject sub
ON r.subjectNo = sub.subjectNo
WHERE subjectName = ‘JAVA‘
ORDER BY studentResult DESC
LIMIT 1,5 --从第2个开始,5个(第一个起始值是0)
本质:在WHERE语句中嵌套一个子查询语句
==查询JAVA的所有考试结果,降序排列
SELECT studentNo,r.subjectNo,studentResult
FROM result r
INNER JOIN subject sub
ON r.subjectNo = sub.subjectNo
WHERE subjectName = ‘JAVA‘
ORDER BY subjectResult DESC
==方法2:使用子查询(有里及外)
SELECT studentNo, subjectNo, subjectResult
FROM result
WHERE subjectNo = (
SELECT subjectNo FROM subject WHERE subjectName = ‘JAVA‘
)
SELECT DISTINCT s.studentNo, studentName
FROM student s
INNER JOIN result r
ON r.studentNo = s.studentNo
WHERE studentResult>=80 AND subjectNo =(
SELECT subjectName FROM subject
WHERE subjectName = ‘JAVA Programming‘
)
=====数学运算
SELECT ABG(-8) ==绝对值
SELECT CEILING(9.4) ==向上取整
SELECT FLOOR(9.4) ==向下取整
SELECT RAND() ==返回一个0-1之间的随机数
SELECT SIGN(-8) ==判断一个数的符号 返回0,-1,1
=====字符串函数
SELECT CHAR_LENGTH(‘shaking chloe‘) ==字符串长度
SELECT CONCAT(‘shaking‘,‘Gladys‘) ==拼接字符串
SELECT INSERT(‘HELLO WORLD‘,1,2,‘SERIEN‘) ==查询,从某个位置开始替换某个长度
SELECT LOWER(‘WDHA‘) ==小写
SELECT UPPER(‘duged‘) ==大写
SELECT INSTR(‘GLADYS‘,‘Y‘) ==返回第一次出现的子串的索引
SELECT PEPLACE(‘越努力,越幸运‘,‘努力‘,‘坚持‘) ==替换出现的指定字符串
SELECT SUBSTRING(‘越努力,越幸运‘,6,2) ==从某位开始截取多少个字符串
SELECT REVERSE(‘越努力,越幸运‘) ==反转字符串
=====查询姓周的同学改为姓何
SELECT REPLACE(studentName,‘周‘,‘何‘) FROM student
WHERE studentName LIKE ‘周%‘
=====时间和日期函数
SELECT CURRENT_DATE() ==获取当前日期
SELECT CURDATE() ==获取当前日期
SELECT NOW() ==获取当前时间
SELECT LOCALTIME() ==本地时间
SELECT SYSDATE() ==系统时间
SELECT YEAR(NOW()) ==还可以用MONTH DAY HOUR MINUTE SECOND
=====系统
SELECT SYSTEM_USER()
SELECT USER()
SELECT VERSION()
函数名称 | 描述 |
---|---|
COUNT() | 计数 |
SUM() | 求和 |
AVG() | 平均值 |
MAX() | 最大值 |
MIN() | 最小值 |
SELECT COUNT(studentName) FROM student; ==COUNT(字段),会忽略所有的null
SELECT COUNT(*); ==不会忽略所有的null
SELECT COUNT(1); ==不会忽略所有的null
/*三个COUNT的区别
列名为主键,count(列名)比COUNT(1)快
列名不为主键,COUNT(1)快
如果表多个列且没有主键,则COUNT(1)的执行效率优于COUNT(*)
如果有主键,COUNT(主键)的执行效率是最优的
如果只有一个字段,那么COUNT(*)最优
*/
=====查询不同课程的平均分、最高分、最低分
==根据不同的课程分组
SELECT subjectName, AVG(studentResult),MAX(studentResult),MIN(studentResult)
FROM result r
INNER JOIN subject sub
ON r.subjectNo = sub.subjectNo
GROUP BY r.subjectNo ==通过什么字段分组
HAVING AVG(studentResult)>80
/*
分组之后的过滤条件要用HAVING
HAVING必须在GROUP BY后面,不能调换顺序
*/
MD5:增强算法复杂度和不可逆性
UPDATE student SET pwd=MD5(pwd) WHERE id=1 ==加密
UPDATE student SET pwd=MD5(pwd) ==加密全部的密码
==插入的时候加密
INSERT INTO student VALUES(104,‘Shaking‘,MD5(‘19970104‘))
==如何校验:将用户传递进来的密码进行MD5加密,然后比对
SELECT * FROM student WHERE name=‘Shaking‘ AND pwd=MD5(‘19970104‘)
将一组SQL放在一个批次中去执行
事务原则:ACID原则 原子性、一致性、隔离性、持久性
Atomicity原子性:要么一起成功,要么一起失败
Consistency一致性:针对一个事务操作前与操作后的状态一致
Isolation隔离性:针对多个用户同时操作,主要是排除其他事务对本次事务的影响
Durability持久性:表示事务结束后的数据不随着外界原因导致数据丢失
? 事务没有提交,恢复到原状
? 事务已经提交,持久化到数据库了,一旦提交就不可逆
指一个事务读取了另外一个事务未提交的数据
在一个事务内读取表中的某一行数据,多次读取结果不同(不一定是错误,只是某些场合不对)
指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致
==mysql是默认开启事务自动提交的
SET autocommit = 0 ==关闭
SET autocommit = 1 ==开启(默认)
==手动处理事务
SET autocommit = 0 ==关闭自动提交
==事务开启
START TRANSACTION ==标记一个事务的开始,从这之后的sql都在一个事务内
INSERT xx
==提交:持久化(成功)
COMMIT
==回滚:回到原来的样子(失败)
ROLLBACK
==事务结束
SET autocommit = 1 ==开启自动提交
====了解即可====
SAVEPOINT name ==设置一个事务的保存点
ROLLBACK TO SAVEPOINT name ==回滚到保存点
RELEASE SAVEPOINT name ==撤销保存点
索引是帮助MySQL高效获取数据的数据结构。
===显示所有的索引信息
SHOW INDEX FROM student
===增加一个索引 索引名(列名)
ALTER TABLE `student` ADD FULLTEXT INDEX `studentName`(studentName);
---CREATE INDEX 索引名 ON 表(字段)
CREATE INDEX id_app_user_name ON app_iser(`name`);
===分析SQL执行的状况
EXPLAIN SELECT * FROM student; ==常规索引,非全文索引
EXPLAIN SELECT * FROM student WHERE MATCH(studentName) AGAINST(‘刘‘);
不加索引的查询是遍历。
索引在大数据的时候区别十分明显
索引的数据结构
Hash类型的索引
Btree:INNODB的默认索引
SQL 命令操作
用户表:mysql.user
本质:对这张表进行增删改查
--创建用户
CREATE USER shaking IDENTIFIED BY ‘123456‘--BY 密码
--修改密码(当前用户)
SET PASSWORD = PASSWORD(‘111111‘)
--修改密码(指定用户)
SET PASSWORD FOR shaking = PASSWORD(‘111111‘)
--重命名
RENAME USER shaking TO chloe
--用户授权
GRANT ALL PRIVILEGES ON *.* TO shaking --全部权限,库.表
--ALL PRIVILEGES除了给别人授权,都能做
--查询权限
SHOW GRANTS FOR shaking --指定用户
SHOW GRANTS FOR root@localhost --root后面要加地址
--撤销权限
REMOVE ALL PRIVILEGES ON *.* FROM shaking
--删除用户
DROP USER shaking
保证重要的数据不丢失
数据转移
备份方式
直接拷贝物理文件
在可视化工具中备份导出
命令行:mysqldump
导出:mysql -h域名 -u用户名 -r密码 库 表1 表2... 导出位置
terminal:mysql -hlocalhost -uroot -p123456 school student /user/desktop
导入:登陆的情况下,切换到指定的数据库
teminal: source /user/desktop/a.sql
当数据库比较复杂的时候
良好的数据库:
为什么需要数据规范化?
三大范式
1NF:要求数据库表的每一列都是不可分割的原子数据项
2NF:满足第一范式的基础上,非码属性必须完全依赖于候选码
确保每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键)
(每张表只描述一件事情)
3NF:在2NF的基础上,任何非主属性不依赖于其他非主属性(消除传递依赖)
确保每一列数据都和主键直接相关,而不是间接相关
关联查询的表不能超过三张表
程序通过数据库驱动操作数据库
为了简化开发人员的(对数据库统一)操作,提供了一个(Java操作数据库)规范,俗称JDBC
开发人员只需要掌握JDBC接口的操作即可
相关包:
Java.sql
Javax.sql
和一个数据库驱动包
public class JdbcFirstDemo{
public static void main(String[] args){
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");//固定写法
//2.用户信息和url
String url = "jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&useSSL=true";//中文&编码&使用安全
String username = "root";
String password = "123456";
//3.连接成功,数据库对象 Connection代表数据库
Connection connection = DriverManager.getConnection(url,uername,password);
//4.执行SQL的对象 Statement 执行SQL的对象
Statement statement = connection.createStatement();
//5.执行SQL的对象 去执行SQL
String sql = "SELECT * FROM users";
ResultSet resultSet = statement.excuteQuery(sql);//返回的结果集,结果集中封装了全部查询出来的结果
while(resultSet.next()){
System.out.println("id="+resultSet.getObject("id"));
System.out.println("name="+resultSet.getObject("NAME"));
}
//6.释放连接
resultSet.close();
statement.close();
connection.close();//消耗资源,用完关掉!
}
}
URL
String url = "jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&useSSL=true";
//jdbc:mysql://localhost:3306/数据库名?参数一&参数二&参数三
Conntecion
//代表数据库
//事务自动提交
connection.setAutoCommit();
//事务提交
connection.commit();
//事务回滚
connection.rollback();
Statement
//执行SQL的对象
//查询操作返回ResultSet
statement.excuteQuery();
//执行任何sql
statement.excute();
//更新、插入、删除都用这个,返回受影响的行数
statement.excuteUpdate();//增删改都用这个
ResultSet
//查询的结果集
//获得指定的数据类型:
//不知道列类型的情况下
resultSet.getObject();
//知道列的类型
resultSet.getString();
resultSet.getInt();
resultSet.getDate();
...
//遍历
//移动光标
resultSet.beforeFirst();//移到最前面
resultSet.afterLast();//移到最后面
resultSet.next();//下一个数据
resultSet.previous();//前一行
resultSet.absolute(row);//指定行
sql存在漏洞,会被攻击导致数据泄漏
SELECT * FROM user WHERE user=‘ ‘ or ‘1=1‘ AND password=‘ ‘ or ‘1=1‘;
这样可以把所有的信息都盗取出来
可以防止sql注入,并且效率更高
把传递进来的参数当作字符
假设其中存在转义字符,会被直接转义
//增删改
public class JdbcFirstDemo{
public static void main(String[] args){
Class.forName("com.mysql.jdbc.Driver");//固定写法
String url = "jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&useSSL=true";//中文&编码&使用安全
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url,uername,password);
//区别
//使用?占位符替代参数
String sql = "INSERT INTO users(id,name,password,birthday) VALUES(?,?,?,?)";
//String sql = "DELETE FROM users WHERE id=?";
//String sql = "UPDATE users SET `name`=? WHERE id=?";
PreparedStatement st = connection.prepareStatement(sql);//预编译SQL,先写SQL,然后不执行
//手动给参数赋值
st.setInt(1, 4);//第一个参数是第几个参数
st.setString(2, "SHAKING");
st.setString(3, "123456");
//sql.Date 数据库
//util.Date Java new Date().getTimw()获得时间戳
st.setDate(4, new java.sql.Date(new Date().getTime()));
//执行
int i = st.executeUpdate();
if(i>0){
System.out.println("插入成功");
}
st.close();
connection.close();//消耗资源,用完关掉!
}
}
//查
String sql = "SELECT * FROM users WHERE id=?";
PreparedStatement st = connection.prepareStatement(sql);
st.setInt(1, 2);
rs = st.executeQuery();
if(rs.next()){
System.out.println(rs.getString("NAME"));
}
要么都成功,要么都失败
ACID 原则
隔离性的问题:
脏读:读取到未提交的事务
不可重复读:在一个事务内,重复读取同一个表格,数据发生了改变
虚读(幻读):在一个事务内,读取到了别人插入的数据,导致前后读出来结果不一致
代码实现
conn.setAutoCommit(false);
关闭自动提交的同时开启事务连接--释放 十分浪费系统资源
池化技术:准备一些预先的资源,过来就连接预先准备好的
最小连接数(常用连接数):10
最大连接数(业务最高承载上限):15
等待超时:100ms
开源数据源实现
DBCP
C3P0
Druid:阿里巴巴
使用这些数据库连接池之后,我们在项目开发中就不需要编写连接数据库的代码了
DBCP: dataSource = BasicDataSourceFactory.createDataSource(properties);
C3P0: dataSource = new ComboPooledDataSource("MySQL");
本质一样,DataSource接口不变,方法就不会变
标签:str 特定 url 第一范式 获得 ast 删除用户 相关 出现
原文地址:https://www.cnblogs.com/GladysJiu/p/14399977.html