0%

积分表结构设计

需求

  • 记录积分行为来源
  • 允许撤销用户积分
  • 个人中心有展示总积分
  • 未来积分可以兑换商品
  • 未来积分逐条过期机制

分析

记录积分行为来源

  • 需要增加行为枚举类,并且记录该行为对应的表主键,比如发帖需要对应发帖的内容 id
  • 行为增加的分数,考虑到变动很少,可以放在代码中。如果后期确认该需求经常变动,需要建立对应的表结构,映射该枚举数据
1
2
3
4
5
6
7
8
9
10
11
12
// 积分枚举
enum ScoreSourceTypeEnum {
LIKE = 1,
COMMENT = 2,
// ...
}
// 积分对应分数
const ScoreSourceTypeMap = new Map({
[ScoreSourceTypeEnum.LIKE]: { score: 5, title: "点赞" },
[ScoreSourceTypeEnum.COMMENT]: { score: 10, title: "评论" },
// ...
});

允许撤销用户积分

  • 撤销用户积分不能修改原始数据,应增加一条新纪录记录该行为,积分记录表允许负值或新建一个表记录数据(先采用允许负值方案)
  • 撤销用户积分需要增加操作人,撤销的原因两个字段
  • 增加一个撤销用户积分的行为,行为对应的主键为对应的积分记录 ID

积分逐条过期机制

  • 积分记录表增加剩余积分字段,默认等于当前积分
  • 用户消费积分时,判断剩余积分字段相加是否满足需求,如果满足,按时间倒序挨个去扣除剩余积分字段,直到扣除完毕
  • 统计用户积分时,SUM(剩余积分)
  • 每天跑一次定时任务,每次用户积分过期,剩余积分字段设置为 0

个人中心展示总积分

  • 每次查询用户的积分数据,都需要 sum 方法,如果数据量多,性能会比较差
  • 是否需要直接统计用户的积分,每次插入或删除去修改该数据
  • 如果需要用户总积分,应独立于用户模块,新建一张表,统计用户总积分,以及用户积分相关数据

未来积分可以兑换商品

  • 积分记录表增加商品兑换的行为,并记录订单 id

其他

  • 并发问题(如果用户同时访问数据,一边增加一边消费,是否需要在数据库加锁,或使用单个消息队列来实现)
  • 积分异常(如果突然出现大批量积分发放,每天应有积分监控功能,每日发放积分、扣减积分,总积分数据)

表结构

用户积分记录表

字段 类型 含义
id primary key 主键
uid bigint 用户 id
source_type tinyint 积分来源类型
source_id int 积分来源 id
socre int 分值
action_uid bigint 操作人
reason varchar 原因
exprie_time datetime 过期时间
rest_score datetime 剩余积分
create_time datetime 创建时间
update_time datetime 更新时间
delete_time datetime 删除时间

用户积分表(暂时不使用,数据库无法支撑再考虑)

字段 类型 含义
id primary key 主键
uid bigint 用户 id
socre int 分值
create_time datetime 创建时间
update_time datetime 更新时间
delete_time datetime 删除时间

不知道应该写点什么。

想到哪 写到哪吧。

都会有一段令人铭记在心的记忆,随着时间的流逝,逐渐变淡

已经感觉到了

最开始

后来

现在

嗯,这种情况也许不会持续很久。

也许到了说再见的时候

不知道是不是最坏的结果

但应该不是最好的

总是有些东西,在夜深人静的时候会想起来

想起来 就一阵难过

为什么当时热恋的两个人

到现在

em,可能分离两地的结果就是这样子吧

自己也没想着尝试改变什么

按班就不

一步步走到那个阶段

是你自己选择的

不应该埋怨什么

你当时做这件事情的时候 已经预见到了结果

这不是如你所愿

感性吗

不应该

应该是活该

晚上总是瞎J8想

就这吧

还有吗

木有了,就这。

希望她有更好的生活

路由器梅林clash下载地址

https://github.com/zusterben/plan_c

路由器关闭关键字检测

sed -i ‘s/\tdetect_package/\t# detect_package/g’ /jffs/softcenter/scripts/ks_tar_install.sh

问题

原来服务是稳定运行的,加了一个子进程功能后,服务器经常无法访问。

具体情况

  • 除代码直接返回数据的接口,其他涉及到数据库IO的接口都无法访问
  • 内存占用正常
  • CPU占用正常(待定)
  • 抽风时间不固定
  • 同一套数据库,其他接口服务正常

可能性猜测

  • 数据库连接池有问题
  • 子进程调用方式有问题
  • 定时任务有问题

排查问题

排查日志记录,确定抽风是不是有固定的模式,业务。

  • 最近一次 2022-09-19 07:00:00.0

    1
    2
    3
    4
    5
    6
    85 [info] 2022-09-19 02:14:02.6 [ApiInterceptor] 126ms 61.165.44.200: 1184582: GET: /api/video: {"current":"1","pageSize":"20","orderBy":"id","order":"DESC","isProfessional":"true","isFake":"false"}: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (K    HTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
    86 [info] 2022-09-19 07:00:00.0 [DayReportCron] 数据日报开始发送,是否是生产环境:true
    87 [info] 2022-09-19 07:47:13.10 [ApiInterceptor] 30ms 180.162.130.187: -: GET: /api/sms: {}: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
    88 [info] 2022-09-19 07:47:19.3 [ApiInterceptor] 24ms 180.162.130.187: -: GET: /api/sms: {}: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
    89 [info] 2022-09-19 07:47:25.6 [ApiInterceptor] 22ms 180.162.130.187: -: GET: /api/sms: {}: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
    90 [info] 2022-09-19 09:47:21.8 [NestFactory] Starting Nest application...
  • 倒第二次 2022-09-18 04:00:00.0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    328 [info] 2022-09-18 03:30:02.0 [KeywordMonitorCommunityCron] 收到close事件,子进程收到信号 256120
    329 [info] 2022-09-18 04:00:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    330 [info] 2022-09-18 04:30:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    331 [info] 2022-09-18 05:00:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    332 [info] 2022-09-18 05:30:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    333 [info] 2022-09-18 06:00:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    334 [info] 2022-09-18 06:30:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    335 [info] 2022-09-18 07:00:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    336 [info] 2022-09-18 07:00:00.0 [DayReportCron] 数据日报开始发送,是否是生产环境:true
    337 [info] 2022-09-18 07:30:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    338 [info] 2022-09-18 08:00:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    339 [info] 2022-09-18 08:30:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    340 [info] 2022-09-18 09:00:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    341 [info] 2022-09-18 09:30:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    342 [info] 2022-09-18 10:00:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    343 [warn] 2022-09-18 10:04:53.5 [exceptionLogger] ::ffff:127.0.0.1: -: GET: /: {} A0002: 用户未登录
    344 [warn] 2022-09-18 10:05:36.3 [exceptionLogger] ::ffff:127.0.0.1: -: GET: /: {} A0002: 用户未登录
    345 [info] 2022-09-18 10:05:43.10 [ApiInterceptor] 27ms 117.143.104.164: -: GET: /api/sms: {}: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
    346 [info] 2022-09-18 10:07:57.10 [ApiInterceptor] 30ms 117.143.104.164: -: GET: /api/sms: {}: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Mobile Safari/537.36
    347 [info] 2022-09-18 10:10:14.10 [ApiInterceptor] 31ms 117.143.104.164: -: GET: /api/sms: {}: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Mobile Safari/537.36
    348 [info] 2022-09-18 10:11:53.2 [ApiInterceptor] 0ms 117.143.104.164: -: GET: /api: {}: apifox/1.0.0 (https://www.apifox.cn)
    349 [warn] 2022-09-18 10:12:28.2 [exceptionLogger] ::1: 1288925: PATCH: /api/configuration/fake-user: {} A0001: 请求参数错误:fakeUsers must be an array
    350 [info] 2022-09-18 10:14:21.3 [ApiInterceptor] 0ms 117.143.104.164: -: GET: /api: {}: apifox/1.0.0 (https://www.apifox.cn)
    351 [info] 2022-09-18 10:14:22.7 [ApiInterceptor] 0ms 117.143.104.164: -: GET: /api: {}: apifox/1.0.0 (https://www.apifox.cn)
    352 [info] 2022-09-18 10:14:23.6 [ApiInterceptor] 0ms 117.143.104.164: -: GET: /api: {}: apifox/1.0.0 (https://www.apifox.cn)
    353 [info] 2022-09-18 10:14:24.4 [ApiInterceptor] 1ms 117.143.104.164: -: GET: /api: {}: apifox/1.0.0 (https://www.apifox.cn)
    354 [info] 2022-09-18 10:16:33.2 [ApiInterceptor] 26ms 117.143.104.164: -: GET: /api/sms: {}: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
    355 [info] 2022-09-18 10:30:00.0 [KeywordMonitorCommunityCron] 社群关键字开始运行
    356 [info] 2022-09-18 10:37:32.3 [NestFactory] Starting Nest application...
  • 倒第三次 2022-09-17 03:30:02.1

    1
    2
    3
    94 [info] 2022-09-17 03:30:02.1 [KeywordMonitorCommunityCron] 收到close事件,子进程收到信号 145279
    95 [info] 2022-09-17 03:35:45.7 [ApiInterceptor] 160ms 61.173.30.240: 1221394: GET: /api/oss/sts: {}: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
    96 [info] 2022-09-17 03:45:34.8 [ApiInterceptor] 141ms 61.173.30.240: 1221394: GET: /api/oss/sts: {}: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36

    日志没有特征,确认是否是定时任务bug

    没有发现问题

暂时定为是数据库连接池问题

确认连接池问题

确认是否是连接池问题

有一段代码循环使用sql,导致连接池被用尽。

尝试复现BUG

1
2
3
4
5
6
7
const value = await PromiseTools.queue(list, async (item) => {
const count = await this.labelCategoryDao.findV2LabelCount(item?.id);
return {
...item,
count,
};
});

如果list过多,会导致连接池全部使用完,无法释放。
PromiseTools.queue代码逻辑有问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static async queue<T, K>(dataList: T[], callBack: (item: T, index: number) => Promise<K> | K, spliceLength = 5): Promise<K[]> {
if (!dataList?.length) {
return [];
}
const promiseList: Array<Promise<K> | K> = [];
const list: K[] = [];
for (let i = 0; i < dataList.length; i++) {
promiseList.push(callBack(dataList[i], i));
}
while (promiseList.length) {
list.push(...(await Promise.all(promiseList.splice(0, spliceLength))));
}
return list;
}

尝试优化代码

JS 中的设计模式

解决一些特定问题的方法,并总结起来,给一个名字。

定义

在面向对象软件设计过程中针对特定的问题简洁而优雅的解决方案。

原则

找出程序中变化的地方,并将变化封装起来

鸭子类型

如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。

多态

同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Duck {}
class Chicken {}
class Dog {}
const makeSound = function (animal) {
if (animal instanceof Duck) {
console.log("嘎嘎嘎");
} else if (animal instanceof Chicken) {
console.log("咯咯咯");
}else if (animal instansof Dog){
console.log("汪汪汪");
}
};
makeSound(new Duck()); // 嘎嘎嘎
makeSound(new Chicken()); // 咯咯咯
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const makeSound = function (animal: Animal) {
animal.sound();
};
class Animal {
sound() {
throw new Error("不存在sound");
}
}
class Duck extends Animal {
sound() {
console.log("嘎嘎嘎");
}
}
class Chicken extends Animal {
sound() {
console.log("嘎嘎嘎");
}
}

class Dog extends Animal {
sound(){
console.log("汪汪汪")
}
}

makeSound(new Duck()); // 嘎嘎嘎
makeSound(new Chicken()); // 咯咯咯
makeSound(new Dog()); // 咯咯咯
// 更多...
// class Chicken extends Animal {
// sound() {
// console.log("嘎嘎嘎");
// }
// }

多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句

封装

考虑你的设计中哪些地方可能变化,这种方式与关注会导致重新设计的原因相反。它不是考虑什么时候会迫使你的设计改变,而是考虑你怎样才能够在不重新设计的情况下进行改变。这里的关键在于封装发生变化的概念,这是许多设计模式的主题

原型模式

基于原型链

单例模式

定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点

场景

  • 需要频繁实例化然后销毁的对象
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象
  • 有状态的工具类对象
  • 繁访问数据库或文件的对象

应用

  • window 对象
  • 日志文件
  • 应用配置
  • 线程池

代理模式

定义

代理模式是为一个对象提供一个代替品或占位符,以便控制对他的访问

场景

  • 明星的经纪人
  • 生产数据库访问
  • 翻墙

迭代器模式

定义

顺序访问一个聚合对象的元素,而不需要暴露对象的内部表示。

基本不需要自己实现,大部分语言内置了迭代器

场景

  • Array.prototype.forEach

发布订阅模式

定义

对象间的一对多的依赖关系,当一个对象的状态发生改变时,所以依赖于他的对象都能接受到通知

场景

  • JS 事件模型

命令模式

定义

命令是对命令的封装,每一个命令都是一个操作,请求方发出请求,接收方接收请求,并执行操作。命令模式解耦了请求方和接收方,命令模式属于行为型模式

场景

  • 处理一些功能,但是不知道请求的接收者是谁,操作是什么,希望以一种松耦合的方式来设计程序。
  • JS 高阶函数

组合模式

定义

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结

场景

  • 树形菜单
  • 文件
  • 文件夹的管理

模板模式

定义

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

场景

  • 造房子
  • 泡茶还是泡咖啡

策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

计算绩效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const calculateBonus = function (
performanceLevel: "S" | "A" | "B",
salary: number
) {
if (performanceLevel === "S") {
return salary * 4;
}
if (performanceLevel === "A") {
return salary * 3;
}
if (performanceLevel === "B") {
return salary * 2;
}
};
calculateBonus("B", 20000); // 输出:40000

// ts实现
const bounsMap = new Map([
["S", (salary: number) => salary * 4],
["A", (salary: number) => salary * 3],
["B", (salary: number) => salary * 2],
]);
bounsMap.get("S")?.(1000);

// 完整实现
enum strategyEnum {
S = "S",
A = "A",
B = "B",
}
const bounsMap = new Map<strategyEnum, { times: number }>([
[strategyEnum.S, { times: 4 }],
[strategyEnum.B, { times: 3 }],
[strategyEnum.B, { times: 2 }],
]);

const getBouns = (strategy: strategyEnum, salary: number) => {
const bouns = bounsMap.get(strategy);
if (!bouns) {
return 0;
}
return bouns.times * salary;
};
getBouns(strategyEnum.S, 1000);

位 1 的个数

题目

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

提示:

  • 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
  • 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。

示例 1:

输入:00000000000000000000000000001011

输出:3

解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 ‘1’。

示例 2:

输入:00000000000000000000000010000000

输出:1

解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 ‘1’。

示例 3:

输入:11111111111111111111111111111101

输出:31

解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 ‘1’。

提示:

输入必须是长度为 32 的 二进制串 。

进阶:

如果多次调用这个函数,你将如何优化你的算法?

题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 自己写的 0ms 2mb
impl Solution {
pub fn hammingWeight (n: u32) -> i32 {
let mut count = 0;
let mut i = 1;
while i <= n && i != 0 {
if n & i != 0 {
count += 1;
}
i = i << 1;
}
count
}
}
1
2
3
4
5
6
7
8
9
10
11
// 构造32位蒙版(只有一个1),进行与操作
impl Solution {
pub fn hammingWeight (n: u32) -> i32 {
let (mut ret, mut mask) = (0, 1u32);
for i in 0..32 {
if n & mask != 0 { ret += 1; }
mask = mask << 1;
}
ret
}
}
1
2
3
4
5
6
// count_one 方法 可以直接获取数量
impl Solution {
pub fn hammingWeight (n: u32) -> i32 {
n.count_ones() as i32
}
}

思路

  • 位的与方法判断是否是 1,是的话,数量加 1
  • 位移的方式变更 1 的位置
  • 位移可能会越界,超出最大值的时候位移数据就是 0,需要注意一下
  • 别人的操作是真的牛皮

相关连接

最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

示例 1:

输入:strs = [“flower”,”flow”,”flight”]

输出:”fl”

示例 2:

输入:strs = [“dog”,”racecar”,”car”]

输出:””

解释:输入不存在公共前缀。

提示:

  • 1 <= strs.length <= 200
  • 0 <= strs[i].length <= 200
  • strs[i] 仅由小写英文字母组成

题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
impl Solution {
pub fn longest_common_prefix(strs: Vec<String>) -> String {
let mut result = String::from("");
let mut index = 0;
'looper: loop {
let mut _temp = "";
for str in &strs {
if str.len() == 0 {
break 'looper;
}
if index >= str.len() {
break 'looper;
}
let c_val = &str[index..index + 1];
if c_val == "" {
break 'looper;
}
if _temp == "" {
_temp = c_val;
continue;
}
if _temp != c_val {
break 'looper;
}
_temp = c_val;
}
result.push_str(&strs.get(0).unwrap()[index..index + 1]);
index += 1;
}
result
}
}

解释

  • 找出所有字符串的第{index}个元素,判断是否全部相等。
  • 如果相等放入结果中,继续循环
  • 不相等跳出外层循环,返回结果
  • 0ms,2.1mb

来源

JS 中的数据结构

数组

优点

  • 原型方法较多,可以比较方便的实现各种功能
  • JSON 支持
  • 相比 java,数组的长度不是固定的,不存在越界问题
  • 兼容性好

缺点

  • 没有获取到索引的情况下,查询效率相对低,时间复杂度为 O(n)
  • 插入数据效率低O(n)
  • 删除数据效率O(n)

应用场景

  • 绝大部分数据处理
  • JSON 支持,可以直接在接口中返回
  • 前端列表渲染

对象

优点

  • 兼容性好
  • 查询效率高,时间复杂度为 O(1)
  • JSON 支持
  • 相对 Map 可以继承原型链上的属性

缺点

  • 相对 MAP 键值只能是 string
  • 相对 MAP 顺序不确定
  • 相对 MAP 没有迭代属性

应用场景

  • 通用场景
  • 需要一个固定 key 来做区分数据内容

MAP

优点

  • 查询效率高,时间复杂度为 O(1)
  • 相对对象这种结构,数据更清晰,效率更高
  • 相对 OBJECT,语义化更好,支持功能多

缺点

  • 原型方法没有数组多
  • JSON 不支持,如果作为接口数据返回,需要重新转换为数组或对象

应用场景

  • 需要一个固定格式的 key value

链表

优点

  • 插入性能比数组高 O(1)
  • 删除性能比数组高 O(1)

缺点

  • 索引效率低 O(n)
  • 原生不支持,需要自己实现链表结构

应用场景

  • 动态插入与删除比较多的场景

应用场景

  • 先进后出
  • JS 执行顺序

队列

应用场景

  • 先进先出
  • 排队场景
  • 性能瓶颈的时候,使用队列,排队处理数据

位图

应用场景

  • 打标签的场景

应用场景

  • DOM 树、CSS 树
  • 多层JSON
  • 级联菜单

尝试着去分享更多的东西

起码留下点什么

加油

为众人抱薪者,不可使其冻毙于风雪;

为大众谋福利者,不可使其孤军奋战;

为自由开路者,不可使其困顿于荆棘。

如果天空是黑暗的,那就摸黑生存;如果发出声音是危险的,那就保持沉默;如果自觉无力发光,那就蜷伏于墙角。但是,不要习惯了黑暗就为黑暗辩护,也不要为自己的苟且而得意,不要嘲讽那些比自己更勇敢的人。我们可以卑微如尘土,但不可扭曲如蛆虫