81 changed files with 1729 additions and 1479 deletions
-
60zhangbo/第九天技术学习笔记.txt
-
61zhangbo/第九天股票知识学习笔记.txt
-
0吴光慧学习笔记/11.3/test1.vue
-
0吴光慧学习笔记/11.3/test2.vue
-
0吴光慧学习笔记/11.3/test3.vue
-
0吴光慧学习笔记/11.3/test4.vue
-
0吴光慧学习笔记/11.4/props.vue
-
0吴光慧学习笔记/11.4/test1.vue
-
0吴光慧学习笔记/11.4/test2.vue
-
BIN吴光慧学习笔记/11.5/11.5学习总结-吴光慧.docx
-
70吴光慧学习笔记/11.5/Count.txt
-
38吴光慧学习笔记/11.5/LoveTalk.txt
-
30吴光慧学习笔记/11.5/query.txt
-
2吴光慧学习笔记/11.5/备份/About.vue
-
12吴光慧学习笔记/11.5/备份/App.txt
-
30吴光慧学习笔记/11.5/备份/Detail.vue
-
10吴光慧学习笔记/11.5/备份/Home.vue
-
61吴光慧学习笔记/11.5/备份/News.txt
-
0吴光慧学习笔记/11.5/备份/hooks.vue
-
21吴光慧学习笔记/11.5/备份/index.ts
-
42吴光慧学习笔记/11.5/备份/router/index.ts
-
BIN吴光慧学习笔记/11.5/道氏理论定义及基本观点.docx
-
4吴光慧学习笔记/hello_vue3/node_modules/.bin/nanoid
-
2吴光慧学习笔记/hello_vue3/node_modules/.bin/nanoid.cmd
-
8吴光慧学习笔记/hello_vue3/node_modules/.bin/nanoid.ps1
-
121吴光慧学习笔记/hello_vue3/node_modules/.package-lock.json
-
31吴光慧学习笔记/hello_vue3/node_modules/.vite/deps/_metadata.json
-
1吴光慧学习笔记/hello_vue3/node_modules/.vite/deps/vue.js
-
16吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/.bin/nanoid
-
17吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/.bin/nanoid.cmd
-
28吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/.bin/nanoid.ps1
-
20吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/LICENSE
-
38吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/README.md
-
45吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/bin/nanoid.js
-
29吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/index.browser.js
-
106吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/index.d.ts
-
47吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/index.js
-
1吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/nanoid.js
-
48吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/non-secure/index.d.ts
-
21吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/non-secure/index.js
-
43吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/package.json
-
2吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/url-alphabet/index.js
-
17吴光慧学习笔记/hello_vue3/node_modules/nanoid/README.md
-
69吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.browser.cjs
-
34吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.browser.js
-
71吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.cjs
-
56吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.d.ts
-
35吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.js
-
26吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.native.js
-
12吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/package.json
-
55吴光慧学习笔记/hello_vue3/node_modules/nanoid/bin/nanoid.cjs
-
72吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.browser.cjs
-
35吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.browser.js
-
85吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.cjs
-
91吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.d.cts
-
25吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.d.ts
-
28吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.js
-
2吴光慧学习笔记/hello_vue3/node_modules/nanoid/nanoid.js
-
34吴光慧学习笔记/hello_vue3/node_modules/nanoid/non-secure/index.cjs
-
23吴光慧学习笔记/hello_vue3/node_modules/nanoid/non-secure/index.d.ts
-
6吴光慧学习笔记/hello_vue3/node_modules/nanoid/non-secure/index.js
-
6吴光慧学习笔记/hello_vue3/node_modules/nanoid/non-secure/package.json
-
76吴光慧学习笔记/hello_vue3/node_modules/nanoid/package.json
-
7吴光慧学习笔记/hello_vue3/node_modules/nanoid/url-alphabet/index.cjs
-
3吴光慧学习笔记/hello_vue3/node_modules/nanoid/url-alphabet/index.js
-
6吴光慧学习笔记/hello_vue3/node_modules/nanoid/url-alphabet/package.json
-
125吴光慧学习笔记/hello_vue3/package-lock.json
-
4吴光慧学习笔记/hello_vue3/package.json
-
71吴光慧学习笔记/hello_vue3/src/App.vue
-
47吴光慧学习笔记/hello_vue3/src/components/Count.vue
-
45吴光慧学习笔记/hello_vue3/src/components/LoveTalk.vue
-
34吴光慧学习笔记/hello_vue3/src/components/News.vue
-
48吴光慧学习笔记/hello_vue3/src/components/Person.vue
-
13吴光慧学习笔记/hello_vue3/src/main.ts
-
29吴光慧学习笔记/hello_vue3/src/store/count.ts
-
22吴光慧学习笔记/hello_vue3/src/store/loveTalk.ts
-
BIN尹顺宇学习笔记/尹顺宇11.05作业/尹顺宇11.05AI探牛深入学习.docx
-
BIN尹顺宇学习笔记/尹顺宇11.05作业/尹顺宇11.05学习总结.docx
-
387尹顺宇学习笔记/尹顺宇11.05作业/尹顺宇11.05学习笔记.md
-
BIN尹顺宇学习笔记/尹顺宇11.05作业/尹顺宇11.05学习笔记.pdf
-
434股票知识评测系统.vue
@ -0,0 +1,60 @@ |
|||||
|
Spring框架复习笔记整理以“三层架构”为核心 |
||||
|
|
||||
|
Spring框架下的三层架构 |
||||
|
一、三层架构核心概念 |
||||
|
三层架构是项目后端开发的经典分层结构,它将业务逻辑按职责划分为三个独立层次,目的是降低代码耦合度、提高可维护性和可扩展性。这三个层次自上而下分别是**表现层(Controller)**、**业务逻辑层(Service)** 和**数据访问层(Dao)**,每层仅与上下相邻层交互,不跨层调用。 |
||||
|
|
||||
|
在Spring框架中,三层架构的实现完全依赖于IoC容器和注解,每层的Bean都由Spring统一管理,层与层之间通过依赖注入(@Autowired/@Resource)建立关联。 |
||||
|
|
||||
|
二、各层职责与Spring实现 |
||||
|
1. 表现层(Controller层) |
||||
|
核心职责:接收前端请求、参数校验、调用Service层方法、封装响应数据(如JSON)返回给前端。它是用户与系统交互的入口,不处理复杂业务逻辑。 |
||||
|
Spring实现方式: |
||||
|
使用`@Controller`或`@RestController`注解声明类为表现层Bean,纳入Spring IoC容器。 |
||||
|
通过`@RequestMapping`、`@GetMapping`、`@PostMapping`等注解映射前端请求路径和请求方式。 |
||||
|
方法参数通过`@RequestParam`(获取URL参数)、`@RequestBody`(获取JSON请求体)等注解接收前端数据。 |
||||
|
|
||||
|
2. 业务逻辑层(Service层) |
||||
|
核心职责:处理核心业务逻辑(如用户注册时的合法性校验、订单生成时的库存扣减)、协调多个Dao层方法完成复杂业务、事务管理(如转账时的原子性保证)。它是系统的“大脑”,承上启下。 |
||||
|
Spring实现方式: |
||||
|
使用`@Service`注解声明类为业务层Bean,纳入IoC容器。 |
||||
|
业务层通常包含“接口+实现类”的结构(如`UserService`接口和`UserServiceImpl`实现类),接口定义方法,实现类写具体逻辑,降低耦合。 |
||||
|
通过`@Autowired`注入Dao层Bean,调用Dao层方法操作数据。 |
||||
|
使用`@Transactional`注解声明事务,确保业务操作的原子性(如失败回滚)。 |
||||
|
|
||||
|
3. 数据访问层(Dao层/Repository层) |
||||
|
核心职责:直接与数据库交互,执行数据的CRUD操作(Create/Read/Update/Delete),不包含业务逻辑。它是系统与数据库之间的“桥梁”。 |
||||
|
Spring实现方式: |
||||
|
使用`@Repository`注解声明类为数据访问层Bean,纳入IoC容器(若使用MyBatis,`@Mapper`注解可替代,且需配置扫描路径)。 |
||||
|
传统方式:Dao层通过Spring JDBC(如`JdbcTemplate`)执行SQL;实际开发中多结合MyBatis,通过XML或注解(`@Select`/`@Insert`)编写SQL。 |
||||
|
不依赖Service层,仅对外提供数据操作方法,由Service层调用。 |
||||
|
|
||||
|
三、三层架构的请求流转流程 |
||||
|
以“用户查询”功能为例,完整的请求流转如下: |
||||
|
1. 前端发送GET请求:`http://localhost:8080/user/1`,请求查询ID为1的用户。 |
||||
|
2. Spring MVC拦截请求,根据`@RequestMapping("/user")`和`@GetMapping("/{id}")`,将请求分发到`UserController`的`getUserById`方法。 |
||||
|
3. `UserController`通过`@PathVariable`接收参数`id=1`,调用注入的`UserService`的`getUserById`方法。 |
||||
|
4. `UserService`(`UserServiceImpl`)调用注入的`UserDao`的`selectById`方法,请求数据。 |
||||
|
5. `UserDao`执行SQL查询数据库,返回`User`对象给`UserService`。 |
||||
|
6. `UserService`将`User`对象返回给`UserController`。 |
||||
|
7. `UserController`将`User`对象封装成统一响应格式(如`Result<User>`),返回JSON给前端。 |
||||
|
|
||||
|
四、三层架构的优势与注意事项 |
||||
|
1. 核心优势 |
||||
|
低耦合:每层职责单一,修改某一层(如更换数据库,仅改Dao层)不影响其他层。 |
||||
|
高可维护:代码按功能分层,定位问题时可快速锁定到具体层(如前端报错查Controller,数据错误查Dao)。 |
||||
|
高可扩展:支持分层扩展,如增加“用户积分”功能,仅需新增`PointService`和`PointDao`,不修改现有代码。 |
||||
|
|
||||
|
2. 注意事项 |
||||
|
禁止跨层调用:不能直接在Controller层调用Dao层,必须通过Service层中转,否则会破坏分层逻辑,增加耦合。 |
||||
|
事务声明在Service层:事务必须加在Service层方法上,因为Service层是业务逻辑的处理单元,Controller层仅负责请求转发,不适合管理事务。 |
||||
|
Bean命名规范:各层Bean命名建议加前缀,如`UserController`、`UserService`、`UserDao`,提高代码可读性。 |
||||
|
|
||||
|
五、三层架构与MVC的区别 |
||||
|
三层架构和MVC,两者本质是不同维度的设计思想,核心区别如下: |
||||
|
|
||||
|
| 对比维度 | 三层架构 | MVC | |
||||
|
|---------- |---------------------------------------------|------------------------------------------------------| |
||||
|
| 设计范围 | 后端代码分层(Controller/Service/Dao) | 前后端交互架构(Model/View/Controller) | |
||||
|
| 核心目的 | 降低后端代码耦合,优化后端开发流程 | 分离前端视图与后端数据、逻辑,优化前后端交互 | |
||||
|
| 对应关系 | 三层架构的“表现层(Controller)”对应MVC的“Controller”;三层架构的“Service+Dao”对应MVC的“Model”;MVC的“View”是前端页面(三层架构不包含前端) | |
||||
@ -0,0 +1,61 @@ |
|||||
|
股票知识复习 |
||||
|
|
||||
|
股票基础 |
||||
|
A 股,全称人民币普通股票,如同寻宝世界里最常见的宝藏线索,是在中国境内注册、上市,以人民币标明面值,供境内投资者用人民币认购和交易的普通股股票。我们日常炒股,大多围绕 A 股展开。在这个市场中,有两类主要 “玩家”。 |
||||
|
大股东,如同夺宝团队中的核心成员,手握公司 5% 以上的股票,拥有巨大影响力。他们的买卖行为,就像核心成员的关键决策,直接左右股价走势。比如,当大股东减持股票,就如同核心成员决定放弃部分宝藏,市场会认为公司前景可能不佳,股价往往随之下跌。 |
||||
|
而散户,恰似寻宝世界里的小探险家,资金量小,单个操作对股价影响微弱。但我们不能忽视大股东的一举一动,要像关注核心成员动向一样,通过 “股东变动公告”,捕捉股价变化的信号,以此调整自己的投资策略。 |
||||
|
|
||||
|
交易规则要点速览 |
||||
|
在股票交易中,开盘价是每天早上 9 点 30 分开市后的第一笔成交价格,它像清晨的第一缕阳光,透露着早盘投资者的情绪。若开盘价上涨,表明早盘买入意愿强烈,大家对当天行情充满期待;反之,则可能意味着市场情绪谨慎。 |
||||
|
收盘价是下午 3 点收盘时的最后一笔成交价格(深市通过集合竞价确定),它是当天交易的终点,也是判断当日行情的重要基准,如同一天冒险结束后的总结,反映了当天市场的最终博弈结果。 |
||||
|
涨跌停制度则是市场的 “安全阀”,普通股票每日涨跌幅限制在 10%(ST 股为 5%)。涨停时,股票价格达到当日上限,虽可卖出但难以买入,如同宝藏被暂时封存,难以获取;跌停时,价格降至下限,能买却难卖,仿佛陷入困境。这种限制防止了股价的过度波动,维护了市场的相对稳定。 |
||||
|
成交量和成交额是判断行情真假的关键指标。成交量指一天内成交的股票数量,成交额是成交量与股价的乘积。就像寻宝时,不仅要关注找到的宝藏数量,还要考虑其价值。股价上涨时,若成交量未同步放大,如同发现的宝藏看似珍贵却缺乏足够的支撑,可能是 “假涨”;若股价上涨且成交量翻倍,则表明有大量资金涌入,如同众多寻宝者竞相追逐,是行情真实上涨的有力证据。 |
||||
|
|
||||
|
洞察市场大环境 |
||||
|
股票市场如同神秘的寻宝大陆,有着明显的牛熊周期。牛市时,市场长期上涨,投资者信心爆棚,仿佛置身遍地宝藏的乐园,赚钱似乎轻而易举,如 2020 年下半年的市场行情,许多股票价格持续攀升,投资者收获颇丰。 |
||||
|
熊市则恰恰相反,市场长期下跌,恐惧笼罩着每一个投资者,大家纷纷担忧财富缩水,就像进入了危机四伏的黑暗森林。以 2018 年为例,股市持续低迷,新手若在熊市高位入场,就如同在危险区域盲目寻宝,极易被深度套牢。 |
||||
|
要了解市场整体情况,就需关注关键指数。上证综指反映上海股市的整体表现,深证成指展现深圳股市的全貌。它们如同寻宝地图上的重要地标,通过其涨跌,我们能知晓市场的大致走向。当上证综指上涨 1%,意味着上海市场多数股票上涨;反之,则多数股票下跌,帮助我们把握市场的整体脉搏。 |
||||
|
|
||||
|
评估股票价值 |
||||
|
市盈率(PE)是衡量股票价值的常用指标,计算公式为 “股价 ÷ 每股收益”,简单来说,就是你为获取 1 元利润愿意付出的成本。低市盈率的股票通常被认为价格相对便宜,但不同行业的市盈率存在差异。科技股由于增长潜力大,投资者愿意为其未来收益支付更高价格,市盈率往往高于传统的银行股。因此,在比较市盈率时,需考虑行业特性,不能一概而论。 |
||||
|
每股收益(EPS)等于公司总利润除以总股数,代表每一股所获得的利润。这一指标持续增长,表明公司盈利能力不断增强,如同宝藏的价值在不断提升,股价也更有可能上涨,吸引更多投资者关注。 |
||||
|
股息率则是(每股分红 ÷ 股价)×100%,反映了股票的分红收益水平。对于追求稳定收益的投资者,如退休长辈,股息率较高的股票颇具吸引力,就像稳定的宝藏产出。银行股、水电燃气股等行业,通常具有较高的股息率,能为投资者提供相对稳定的现金流回报 。 |
||||
|
|
||||
|
AI 工具:股票投资的夺宝神器 |
||||
|
在变幻莫测的股票市场中,我们就如同深入神秘遗迹的夺宝奇兵,每一次决策都关乎着财富的得失。而如今,AI 工具就像是我们手中的神奇罗盘,为我们指引着投资的方向。它能够快速处理海量的数据,精准捕捉市场的细微变化,帮助我们在复杂的股市中做出更明智的决策。接下来,让我们一同深入探索夺宝奇兵中那些强大的 AI 工具,看看它们是如何助力我们在股市中披荆斩棘,斩获财富的。 |
||||
|
|
||||
|
夺宝利剑:捕捉股票上涨信号 |
||||
|
夺宝利剑是一款极具特色的 AI 工具,它依靠 4 根线来精准判断股票的趋势,每根线都肩负着独特的使命,如同寻宝团队中分工明确的成员,协同为我们传递着股票涨跌的重要信息。 |
||||
|
天线,作为长期趋势的瞭望者,时刻关注着股票的长期走势。当这根线持续向上攀升时,就如同寻宝地图上一条清晰指向宝藏的路线,表明股票在长期内处于上涨趋势,即便短期内出现些许波动,也无需过度担忧。 |
||||
|
飞线主要负责反映股票的中期情况,它如同寻宝过程中对周边环境的探测,关注着公司未来产能的规划以及行业发展的预期。如果飞线向上,说明公司的发展前景乐观,行业预期良好。 |
||||
|
中线则聚焦于短期资金的动向,是我们观察近期资金流向的重要窗口。若中线向上,意味着近期有资金持续买入,为股票价格的上涨提供了动力。 |
||||
|
流线堪称夺宝利剑的核心,它如同宝藏发出的神秘信号,专门提示股票何时可能出现暴涨行情,是我们捕捉股票启动信号的关键指标。 |
||||
|
当 4 根线紧密靠拢,股价波动幅度不超过 3%,且流线率先向上冲刺时,这便是强启动信号,如同宝藏的大门已经缓缓开启,我们应迅速予以关注。此时,多空双方意见高度一致,资金集中涌入,股票极有可能迎来主升浪,开启大幅上涨之旅。 |
||||
|
而当只有 3 根线靠在一起,流线尚未向上冲时,这属于弱启动信号,表明虽然长期或中期趋势向好,但还需耐心等待流线也向上运行,才能确认买入时机,以免过早入场导致踏空。 |
||||
|
在实战过程中,我们要时刻牢记,只要天线没有向下拐头,就说明股票的长期趋势依然向好,短期的下跌只是暂时的调整,无需惊慌抛售。但如果流线突然向上冲,而飞线和天线却向下,这大概率是一个假信号,就像在寻宝途中遇到的虚假线索,此时应谨慎对待,避免盲目追涨。 |
||||
|
以 N 奕材 - U 这只半导体股为例,由于行业景气度高,公司实力强劲,其天线陡峭向上,彰显出长期上涨的趋势。飞线也因公司产能扩张的预期而同步向上。中线则因为股票交易活跃,换手率高达 60% 以上而呈现上升态势。在上市开盘后的 15 分钟内,流线迅速与其他 3 根线汇聚,精准地捕捉到了上市首日股价近 200% 的涨幅,从 10 块左右一路飙升至 30 块,为投资者带来了丰厚的收益。 |
||||
|
|
||||
|
AI 探牛:辨别股票涨跌真伪 |
||||
|
AI 探牛如同一位经验丰富的寻宝导师,凭借独特的判断逻辑,帮助我们分清股票的 “真涨” 与 “假反弹”,避免在投资中误入陷阱。 |
||||
|
它主要依靠牵牛绳和 K 线属性这两个关键要素来判断股票的涨跌趋势。牵牛绳就像是我们手中的指南针,红色代表股票处于上涨趋势,绿色则表示下跌趋势,为我们确定了股票走势的大方向。 |
||||
|
K 线属性则如同宝藏的不同特征,通过不同的颜色来传递股票的不同状态。红色进攻 K 线表明买入的主力正在发力,如同寻宝者全力挖掘宝藏;蓝色推进 K 线意味着股票稳步上涨,无需过度担忧;黄色防守 K 线则警示我们股票快扛不住下跌的压力,要小心谨慎;青色撤退 K 线更是明确地提醒我们赶紧卖出,股票即将下跌。 |
||||
|
为了避免被短期的波动所迷惑,AI 探牛采用了跨周期验证的方法。比如,当某一天股票显示为红线且是进攻 K 线,但从一周的时间跨度来看却是绿线,这很可能只是短期的反弹,此时不宜大量买入。相反,如果当天和一周内都呈现红线且是进攻 K 线,说明大周期对小周期形成了有力的支撑,股票上涨的态势更为稳健。 |
||||
|
在判断股票回调时,AI 探牛也有明确的规则。当出现绿线和撤退 K 线时,如果股票跌幅不到 5%,且未跌破一周的趋势线,这通常只是短期的回调,无需匆忙卖出。但如果跌幅超过 8%,且一周趋势线也开始向下拐头,那就必须果断止损,以免遭受更大的损失。 |
||||
|
对于成长股而言,由于其波动较大,牵牛绳对股价变化的反应更为灵敏。例如,当股票一天涨幅达到 50% 时,牵牛绳就会迅速从绿色转为红色。而进攻 K 线的确认则更为严格,要求第一天涨幅超过 50%,并且收盘价能站在当天最高涨幅的 80% 以上,以此确保上涨信号的真实性,避免被虚假的上涨所误导。 |
||||
|
|
||||
|
AI 雷达:寻找股票投资机会 |
||||
|
AI 雷达就像是寻宝过程中的探测仪,帮助我们精准寻找股票的 “底部” 和 “加速涨” 的机会,为我们在股市中把握最佳的投资时机。 |
||||
|
它主要通过两根线和 K 线形态来进行判断。天轨(蓝色)代表着买的主力能够撑住的价格,如同宝藏上方的一道阻力,股票涨到这个位置可能会受到阻碍;地轨(黄色)则表示卖的主力能压到的价格,是股票的支撑位,当股价跌到这个位置时可能会止跌反弹。 |
||||
|
当出现底部启动信号时,就意味着我们可能找到了宝藏的入口,是买入股票的好时机。底部启动需要满足三个条件:一是跌的阴线数量比上一波更多,表明市场的下跌动能在逐渐释放;二是股价跌到了新的低点,显示出市场的恐慌情绪达到一定程度;三是地轨往上走或者天地轨平着,这说明卖的主力力量已经逐渐耗尽。在满足这些条件后,还需要等待两根阳线的出现。第一根是 “试盘阳线”,这是主力在试探市场的买入意愿;第二根 “启动阳线” 则确认了底部的形成,此时我们就可以放心买入。 |
||||
|
而当股票处于上涨过程中,如果天轨从平着变为往上走,对应出现的阳线就是 “拉升阳线”,这表明买的主力开始发力,如同寻宝者找到了推动宝藏上升的力量,股票即将进入加速涨阶段,我们应紧紧抓住这个机会,获取更多的收益。 |
||||
|
然而,AI 雷达目前还存在一些待完善的地方。例如,在股票跌破地轨后,如何确定止损的标准,是跌幅达到一定比例还是持续下跌一定天数后卖出,还需要进一步明确。在震荡行情中,如何分辨真假信号,比如限定股价下跌的时间以及成交量的阈值等,也需要进一步优化。此外,不同类型的股票具有不同的特性,小盘股周期较短,蓝筹股则需要更多的成交量才能有效,因此如何针对不同股票适配不同的参数,也是未来需要解决的问题。 |
||||
|
|
||||
|
超级云脑:实现全流程决策 |
||||
|
超级云脑堪称散户专属的 “智能军师”,它如同一个智慧的宝库,为我们提供全方位的投资决策支持,帮助我们在股市中实现全流程的精准决策。 |
||||
|
超级云脑分为三层结构,每一层都承担着独特的功能。最核心的超级云脑层,运用强大的数据分析能力,精准算出股票的估值、安全等级等关键指标,为我们的投资决策提供坚实的数据基础。 |
||||
|
云脑探秘层则以直观的图表形式展示 6 个关键信息,让我们一目了然。在这里,我们可以清晰地了解股票的估值情况,通过对比 PE/PB/EPS 等指标,判断股票是贵了、刚好还是便宜了。例如,如果一只股票的 PE 比行业平均水平低,那就说明它相对便宜,具有一定的投资价值。 |
||||
|
安全级别通过 6 种颜色分为 5 级风险,红色代表极高风险,绿色表示非常安全,让我们对投资风险一目了然。压力支撑模块则明确地标出股票 “跌到哪能止跌”(支撑位)、“涨到哪会受阻”(压力位),为我们的买卖操作提供重要参考。 |
||||
|
趋势研判帮助我们分清股票的长期(如半年)和短期(如一周)走势是涨是跌,让我们对股票的未来走向有更清晰的认识。庄家成本模块可以让我们了解主力和散户的成本谁低,如果我们的成本比主力还低,那么在投资中就更具优势,安全性更高。 |
||||
|
情绪价值则如同市场的温度计,提示我们市场是过于狂热(此时应避免追高)、过于恐慌(不要轻易割肉)还是即将发生方向转变,帮助我们把握市场的情绪变化,做出更明智的决策。通过云脑探秘,我们能够全面了解一只股票,解决 “这股票值不值得买、现在能不能买、该拿着还是卖掉” 的问题。 |
||||
|
机构动向层主要用于跟踪主力资金的流向,让我们清楚地知道大资金在做什么。在这里,我们可以看到当日资金的买卖情况,了解主力资金是在买入还是卖出,以及主力是否在偷偷建仓。通过庄散对决模块,我们还能知晓主力和散户谁持有的股票更多。例如,当一只股票显示便宜,同时主力在买入且偷偷建仓时,这就形成了三重利好,是我们重点关注的对象。 |
||||
|
在实际操作中,我们可以按照以下流程进行。首先是选股,重点关注那些估值便宜、安全级别高、主力成本低且机构在买入的股票。然后等待买入时机,当股票跌到支撑位,且市场情绪稳定时,果断买入。在持有过程中,密切关注股票的长期趋势是否发生改变,主力是否还在持仓,筹码是否稳定。最后,当股票涨到压力位时,及时止盈;如果市场出现恐慌情绪,股票走势恶化,则果断止损,确保我们的投资收益。 |
||||
@ -0,0 +1,70 @@ |
|||||
|
<template> |
||||
|
<div class="count"> |
||||
|
<h2>当前求和为:{{ countStore.sum }}</h2> |
||||
|
<select v-model.number="n"> |
||||
|
<option value="1">1</option> |
||||
|
<option value="2">2</option> |
||||
|
<option value="3">3</option> |
||||
|
</select> |
||||
|
<button @click="add">加</button> |
||||
|
<button @click="minus">减</button> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts" name="Count"> |
||||
|
import { ref,reactive } from "vue"; |
||||
|
import {useCountStore} from '@/store/count' |
||||
|
|
||||
|
const countStore = useCountStore() |
||||
|
|
||||
|
// 以下两种方式都可以拿到state中的数据 |
||||
|
// console.log('@@@',countStore.sum) |
||||
|
// console.log('@@@',countStore.$state.sum) |
||||
|
|
||||
|
/* let obj = reactive({ |
||||
|
a:1, |
||||
|
b:2, |
||||
|
c:ref(3) |
||||
|
}) |
||||
|
let x = ref(9) |
||||
|
console.log(obj.a) |
||||
|
console.log(obj.b) |
||||
|
console.log(obj.c) */ |
||||
|
|
||||
|
|
||||
|
// 数据 |
||||
|
let n = ref(1) // 用户选择的数字 |
||||
|
// 方法 |
||||
|
function add(){ |
||||
|
//第一种修改方式 |
||||
|
// countStore.sum += 1 |
||||
|
|
||||
|
//第二种修改方式 |
||||
|
// countStore.$patch({ |
||||
|
// sum:999, |
||||
|
// school:'xxx', |
||||
|
// address:'yyy' |
||||
|
// }) |
||||
|
|
||||
|
//第三种修改方式 |
||||
|
countStore.increment(n.value) |
||||
|
} |
||||
|
|
||||
|
function minus(){ |
||||
|
countStore.decrement(n.value) |
||||
|
|
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.count { |
||||
|
background-color: skyblue; |
||||
|
padding: 10px; |
||||
|
border-radius: 10px; |
||||
|
box-shadow: 0 0 10px; |
||||
|
} |
||||
|
select,button { |
||||
|
margin: 0 5px; |
||||
|
height: 25px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,38 @@ |
|||||
|
<template> |
||||
|
<div class="talk"> |
||||
|
<button @click="getLoveTalk">获取一句土味情话</button> |
||||
|
<ul> |
||||
|
<li v-for="talk in talkList" :key="talk.id">{{talk.title}}</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts" name="LoveTalk"> |
||||
|
import {reactive} from 'vue' |
||||
|
import axios from "axios"; |
||||
|
import {nanoid} from 'nanoid' |
||||
|
// 数据 |
||||
|
let talkList = reactive([ |
||||
|
{id:'ftrfasdf01',title:'今天你有点怪,哪里怪?怪好看的!'}, |
||||
|
{id:'ftrfasdf02',title:'草莓、蓝莓、蔓越莓,今天想我了没?'}, |
||||
|
{id:'ftrfasdf03',title:'心里给你留了一块地,我的死心塌地'} |
||||
|
]) |
||||
|
// 方法 |
||||
|
async function getLoveTalk(){ |
||||
|
// 发请求,下面这行的写法是:连续解构赋值+重命名 |
||||
|
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') |
||||
|
// 把请求回来的字符串,包装成一个对象 |
||||
|
let obj = {id:nanoid(),title} |
||||
|
// 放到数组中 |
||||
|
talkList.unshift(obj) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.talk { |
||||
|
background-color: orange; |
||||
|
padding: 10px; |
||||
|
border-radius: 10px; |
||||
|
box-shadow: 0 0 10px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,30 @@ |
|||||
|
Detail |
||||
|
<template> |
||||
|
<ul class="news-list"> |
||||
|
<li>编号:{{route.query.id}}</li> |
||||
|
<li>标题:{{route.query.title}}</li> |
||||
|
<li>内容:{{route.query.content}}</li> |
||||
|
</ul> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup name="Detail"> |
||||
|
import {useRoute} from 'vue-router' |
||||
|
const route = useRoute() |
||||
|
const {query} = route |
||||
|
// 这样上面就可以写 <li>内容:{{query.content}}</li> |
||||
|
// 打印query参数 |
||||
|
console.log(route.query) |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.news-list{ |
||||
|
list-style: none; |
||||
|
padding-left: 10px; |
||||
|
} |
||||
|
.news-list>li{ |
||||
|
line-height: 30px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,12 @@ |
|||||
|
<template> |
||||
|
<Count/> |
||||
|
<br> |
||||
|
<LoveTalk/> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts" name="App"> |
||||
|
import Count from './components/Count.vue' |
||||
|
import LoveTalk from './components/LoveTalk.vue' |
||||
|
</script> |
||||
|
<style scoped> |
||||
|
</style> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<template> |
||||
|
<ul class="news-list"> |
||||
|
<li>编号:{{id}}</li> |
||||
|
<li>标题:{{title}}</li> |
||||
|
<li>内容:{{content}}</li> |
||||
|
</ul> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup name="Detail"> |
||||
|
// import {useRoute} from 'vue-router' |
||||
|
// const route = useRoute() |
||||
|
// const {query} = route |
||||
|
// // 这样上面就可以写 <li>内容:{{query.content}}</li> |
||||
|
// // 打印query参数 |
||||
|
// console.log(route.query) |
||||
|
|
||||
|
defineProps(['id','title','content']) |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.news-list{ |
||||
|
list-style: none; |
||||
|
padding-left: 10px; |
||||
|
} |
||||
|
.news-list>li{ |
||||
|
line-height: 30px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,61 @@ |
|||||
|
<template> |
||||
|
<div class="news"> |
||||
|
<!--导航区--> |
||||
|
<ul> |
||||
|
<li v-for="news in newsList" :key="news.id"> |
||||
|
<!--第一种写法--> |
||||
|
<!-- <RouterLink :to="`/news/detail?id=${news.id} |
||||
|
&title=${news.title} |
||||
|
&content=${news.content}`">{{news.title}}</RouterLink> --> |
||||
|
|
||||
|
<!-- 第二种写法 --> |
||||
|
<RouterLink |
||||
|
:to="{ |
||||
|
path: '/news/detail', |
||||
|
query: { |
||||
|
id: news.id, |
||||
|
title: news.title, |
||||
|
content: news.content}}"> |
||||
|
{{news.title}} |
||||
|
</RouterLink> |
||||
|
</li> |
||||
|
</ul> |
||||
|
<!--内容区--> |
||||
|
<div class="news-content"> |
||||
|
<RouterView></RouterView> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup name="News"> |
||||
|
import { reactive } from 'vue'; |
||||
|
import { RouterLink, RouterView } from 'vue-router'; |
||||
|
|
||||
|
const newsList = reactive([ |
||||
|
{id: 1, title: '新闻1',content: '新闻1的内容'}, |
||||
|
{id: 2, title: '新闻2',content: '新闻2的内容'}, |
||||
|
{id: 3, title: '新闻3',content: '新闻3的内容'}, |
||||
|
{id: 4, title: '新闻4',content: '新闻4的内容'}, |
||||
|
]) |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.news { |
||||
|
padding: 0 20px; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
height: 100px; |
||||
|
} |
||||
|
.news ul { |
||||
|
margin-top: 30px; |
||||
|
/* list-style: none; */ |
||||
|
padding-left: 10px; |
||||
|
} |
||||
|
.news li>a{ |
||||
|
text-decoration: none; |
||||
|
color: #000; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,42 @@ |
|||||
|
import {createRouter,createWebHistory} from 'vue-router' |
||||
|
import Home from '@/components/Home.vue' |
||||
|
import News from '@/components/News.vue' |
||||
|
import About from '@/components/About.vue' |
||||
|
import Detail from '@/components/Detail.vue' |
||||
|
|
||||
|
const router = createRouter({ |
||||
|
history:createWebHistory(), |
||||
|
routes:[ |
||||
|
{ |
||||
|
name:'zhuye', |
||||
|
path:'/home', |
||||
|
component:Home |
||||
|
}, |
||||
|
{ |
||||
|
name:'xinwen', |
||||
|
path:'/news', |
||||
|
component:News, |
||||
|
children:[ |
||||
|
{ |
||||
|
name:'xiangqing', |
||||
|
path:'detail/:id/:title/:content', |
||||
|
component:Detail, |
||||
|
props(route){ |
||||
|
return route.query |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
|
||||
|
}, |
||||
|
{ |
||||
|
name:'guanyu', |
||||
|
path:'/about', |
||||
|
component:About |
||||
|
}, |
||||
|
{ |
||||
|
path:'/', |
||||
|
redirect:'/home' |
||||
|
} |
||||
|
] |
||||
|
}) |
||||
|
export default router |
||||
@ -1,25 +1,46 @@ |
|||||
{ |
{ |
||||
"hash": "547bafd9", |
|
||||
|
"hash": "39b4f1a2", |
||||
"configHash": "e5b55508", |
"configHash": "e5b55508", |
||||
"lockfileHash": "0d539b07", |
|
||||
"browserHash": "2bc9fe4f", |
|
||||
|
"lockfileHash": "22fdfde4", |
||||
|
"browserHash": "b44b993f", |
||||
"optimized": { |
"optimized": { |
||||
|
"pinia": { |
||||
|
"src": "../../pinia/dist/pinia.mjs", |
||||
|
"file": "pinia.js", |
||||
|
"fileHash": "a97780a2", |
||||
|
"needsInterop": false |
||||
|
}, |
||||
"vue": { |
"vue": { |
||||
"src": "../../vue/dist/vue.runtime.esm-bundler.js", |
"src": "../../vue/dist/vue.runtime.esm-bundler.js", |
||||
"file": "vue.js", |
"file": "vue.js", |
||||
"fileHash": "5b2b4475", |
|
||||
|
"fileHash": "1e96b106", |
||||
"needsInterop": false |
"needsInterop": false |
||||
}, |
}, |
||||
"vue-router": { |
"vue-router": { |
||||
"src": "../../vue-router/dist/vue-router.mjs", |
"src": "../../vue-router/dist/vue-router.mjs", |
||||
"file": "vue-router.js", |
"file": "vue-router.js", |
||||
"fileHash": "832988d5", |
|
||||
|
"fileHash": "0eced000", |
||||
|
"needsInterop": false |
||||
|
}, |
||||
|
"axios": { |
||||
|
"src": "../../axios/index.js", |
||||
|
"file": "axios.js", |
||||
|
"fileHash": "0965a06b", |
||||
|
"needsInterop": false |
||||
|
}, |
||||
|
"nanoid": { |
||||
|
"src": "../../nanoid/index.browser.js", |
||||
|
"file": "nanoid.js", |
||||
|
"fileHash": "39507410", |
||||
"needsInterop": false |
"needsInterop": false |
||||
} |
} |
||||
}, |
}, |
||||
"chunks": { |
"chunks": { |
||||
"chunk-SFGEOVP2": { |
"chunk-SFGEOVP2": { |
||||
"file": "chunk-SFGEOVP2.js" |
"file": "chunk-SFGEOVP2.js" |
||||
|
}, |
||||
|
"chunk-PZ5AY32C": { |
||||
|
"file": "chunk-PZ5AY32C.js" |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -1,16 +0,0 @@ |
|||||
#!/bin/sh |
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") |
|
||||
|
|
||||
case `uname` in |
|
||||
*CYGWIN*|*MINGW*|*MSYS*) |
|
||||
if command -v cygpath > /dev/null 2>&1; then |
|
||||
basedir=`cygpath -w "$basedir"` |
|
||||
fi |
|
||||
;; |
|
||||
esac |
|
||||
|
|
||||
if [ -x "$basedir/node" ]; then |
|
||||
exec "$basedir/node" "$basedir/../nanoid/bin/nanoid.js" "$@" |
|
||||
else |
|
||||
exec node "$basedir/../nanoid/bin/nanoid.js" "$@" |
|
||||
fi |
|
||||
@ -1,17 +0,0 @@ |
|||||
@ECHO off |
|
||||
GOTO start |
|
||||
:find_dp0 |
|
||||
SET dp0=%~dp0 |
|
||||
EXIT /b |
|
||||
:start |
|
||||
SETLOCAL |
|
||||
CALL :find_dp0 |
|
||||
|
|
||||
IF EXIST "%dp0%\node.exe" ( |
|
||||
SET "_prog=%dp0%\node.exe" |
|
||||
) ELSE ( |
|
||||
SET "_prog=node" |
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;% |
|
||||
) |
|
||||
|
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.js" %* |
|
||||
@ -1,28 +0,0 @@ |
|||||
#!/usr/bin/env pwsh |
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent |
|
||||
|
|
||||
$exe="" |
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { |
|
||||
# Fix case when both the Windows and Linux builds of Node |
|
||||
# are installed in the same directory |
|
||||
$exe=".exe" |
|
||||
} |
|
||||
$ret=0 |
|
||||
if (Test-Path "$basedir/node$exe") { |
|
||||
# Support pipeline input |
|
||||
if ($MyInvocation.ExpectingInput) { |
|
||||
$input | & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.js" $args |
|
||||
} else { |
|
||||
& "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.js" $args |
|
||||
} |
|
||||
$ret=$LASTEXITCODE |
|
||||
} else { |
|
||||
# Support pipeline input |
|
||||
if ($MyInvocation.ExpectingInput) { |
|
||||
$input | & "node$exe" "$basedir/../nanoid/bin/nanoid.js" $args |
|
||||
} else { |
|
||||
& "node$exe" "$basedir/../nanoid/bin/nanoid.js" $args |
|
||||
} |
|
||||
$ret=$LASTEXITCODE |
|
||||
} |
|
||||
exit $ret |
|
||||
@ -1,20 +0,0 @@ |
|||||
The MIT License (MIT) |
|
||||
|
|
||||
Copyright 2017 Andrey Sitnik <andrey@sitnik.ru> |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
||||
this software and associated documentation files (the "Software"), to deal in |
|
||||
the Software without restriction, including without limitation the rights to |
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
|
||||
the Software, and to permit persons to whom the Software is furnished to do so, |
|
||||
subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in all |
|
||||
copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
||||
@ -1,38 +0,0 @@ |
|||||
# Nano ID |
|
||||
|
|
||||
<img src="https://ai.github.io/nanoid/logo.svg" align="right" |
|
||||
alt="Nano ID logo by Anton Lovchikov" width="180" height="94"> |
|
||||
|
|
||||
**English** | [日本語](./README.ja.md) | [Русский](./README.ru.md) | [简体中文](./README.zh-CN.md) | [Bahasa Indonesia](./README.id-ID.md) | [한국어](./README.ko.md) |
|
||||
|
|
||||
A tiny, secure, URL-friendly, unique string ID generator for JavaScript. |
|
||||
|
|
||||
> “An amazing level of senseless perfectionism, |
|
||||
> which is simply impossible not to respect.” |
|
||||
|
|
||||
* **Small.** 118 bytes (minified and brotlied). No dependencies. |
|
||||
[Size Limit] controls the size. |
|
||||
* **Safe.** It uses hardware random generator. Can be used in clusters. |
|
||||
* **Short IDs.** It uses a larger alphabet than UUID (`A-Za-z0-9_-`). |
|
||||
So ID size was reduced from 36 to 21 symbols. |
|
||||
* **Portable.** Nano ID was ported |
|
||||
to over [20 programming languages](./README.md#other-programming-languages). |
|
||||
|
|
||||
```js |
|
||||
import { nanoid } from 'nanoid' |
|
||||
model.id = nanoid() //=> "V1StGXR8_Z5jdHi6B-myT" |
|
||||
``` |
|
||||
|
|
||||
--- |
|
||||
|
|
||||
<img src="https://cdn.evilmartians.com/badges/logo-no-label.svg" alt="" width="22" height="16" /> Made at <b><a href="https://evilmartians.com/devtools?utm_source=nanoid&utm_campaign=devtools-button&utm_medium=github">Evil Martians</a></b>, product consulting for <b>developer tools</b>. |
|
||||
|
|
||||
--- |
|
||||
|
|
||||
[online tool]: https://gitpod.io/#https://github.com/ai/nanoid/ |
|
||||
[with Babel]: https://developer.epages.com/blog/coding/how-to-transpile-node-modules-with-babel-and-webpack-in-a-monorepo/ |
|
||||
[Size Limit]: https://github.com/ai/size-limit |
|
||||
|
|
||||
|
|
||||
## Docs |
|
||||
Read full docs **[here](https://github.com/ai/nanoid#readme)**. |
|
||||
@ -1,45 +0,0 @@ |
|||||
#!/usr/bin/env node
|
|
||||
import { customAlphabet, nanoid } from '../index.js' |
|
||||
function print(msg) { |
|
||||
process.stdout.write(msg + '\n') |
|
||||
} |
|
||||
function error(msg) { |
|
||||
process.stderr.write(msg + '\n') |
|
||||
process.exit(1) |
|
||||
} |
|
||||
if (process.argv.includes('--help') || process.argv.includes('-h')) { |
|
||||
print(`Usage
|
|
||||
$ nanoid [options] |
|
||||
Options |
|
||||
-s, --size Generated ID size |
|
||||
-a, --alphabet Alphabet to use |
|
||||
-h, --help Show this help |
|
||||
Examples |
|
||||
$ nanoid -s 15 |
|
||||
S9sBF77U6sDB8Yg |
|
||||
$ nanoid --size 10 --alphabet abc |
|
||||
bcabababca`)
|
|
||||
process.exit() |
|
||||
} |
|
||||
let alphabet, size |
|
||||
for (let i = 2; i < process.argv.length; i++) { |
|
||||
let arg = process.argv[i] |
|
||||
if (arg === '--size' || arg === '-s') { |
|
||||
size = Number(process.argv[i + 1]) |
|
||||
i += 1 |
|
||||
if (Number.isNaN(size) || size <= 0) { |
|
||||
error('Size must be positive integer') |
|
||||
} |
|
||||
} else if (arg === '--alphabet' || arg === '-a') { |
|
||||
alphabet = process.argv[i + 1] |
|
||||
i += 1 |
|
||||
} else { |
|
||||
error('Unknown argument ' + arg) |
|
||||
} |
|
||||
} |
|
||||
if (alphabet) { |
|
||||
let customNanoid = customAlphabet(alphabet, size) |
|
||||
print(customNanoid()) |
|
||||
} else { |
|
||||
print(nanoid(size)) |
|
||||
} |
|
||||
@ -1,29 +0,0 @@ |
|||||
/* @ts-self-types="./index.d.ts" */ |
|
||||
import { urlAlphabet as scopedUrlAlphabet } from './url-alphabet/index.js' |
|
||||
export { urlAlphabet } from './url-alphabet/index.js' |
|
||||
export let random = bytes => crypto.getRandomValues(new Uint8Array(bytes)) |
|
||||
export let customRandom = (alphabet, defaultSize, getRandom) => { |
|
||||
let mask = (2 << Math.log2(alphabet.length - 1)) - 1 |
|
||||
let step = -~((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
return (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = getRandom(step) |
|
||||
let j = step | 0 |
|
||||
while (j--) { |
|
||||
id += alphabet[bytes[j] & mask] || '' |
|
||||
if (id.length >= size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
export let customAlphabet = (alphabet, size = 21) => |
|
||||
customRandom(alphabet, size | 0, random) |
|
||||
export let nanoid = (size = 21) => { |
|
||||
let id = '' |
|
||||
let bytes = crypto.getRandomValues(new Uint8Array((size |= 0))) |
|
||||
while (size--) { |
|
||||
id += scopedUrlAlphabet[bytes[size] & 63] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
@ -1,106 +0,0 @@ |
|||||
/** |
|
||||
* A tiny, secure, URL-friendly, unique string ID generator for JavaScript |
|
||||
* with hardware random generator. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { nanoid } from 'nanoid' |
|
||||
* model.id = nanoid() //=> "V1StGXR8_Z5jdHi6B-myT"
|
|
||||
* ```
|
|
||||
* |
|
||||
* @module |
|
||||
*/ |
|
||||
|
|
||||
/** |
|
||||
* Generate secure URL-friendly unique ID. |
|
||||
* |
|
||||
* By default, the ID will have 21 symbols to have a collision probability |
|
||||
* similar to UUID v4. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { nanoid } from 'nanoid' |
|
||||
* model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL"
|
|
||||
* ```
|
|
||||
* |
|
||||
* @param size Size of the ID. The default size is 21. |
|
||||
* @typeparam Type The ID type to replace `string` with some opaque type. |
|
||||
* @returns A random string. |
|
||||
*/ |
|
||||
export function nanoid<Type extends string>(size?: number): Type |
|
||||
|
|
||||
/** |
|
||||
* Generate secure unique ID with custom alphabet. |
|
||||
* |
|
||||
* Alphabet must contain 256 symbols or less. Otherwise, the generator |
|
||||
* will not be secure. |
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate the ID. |
|
||||
* @param defaultSize Size of the ID. The default size is 21. |
|
||||
* @typeparam Type The ID type to replace `string` with some opaque type. |
|
||||
* @returns A random string generator. |
|
||||
* |
|
||||
* ```js
|
|
||||
* const { customAlphabet } = require('nanoid') |
|
||||
* const nanoid = customAlphabet('0123456789абвгдеё', 5) |
|
||||
* nanoid() //=> "8ё56а"
|
|
||||
* ```
|
|
||||
*/ |
|
||||
export function customAlphabet<Type extends string>( |
|
||||
alphabet: string, |
|
||||
defaultSize?: number |
|
||||
): (size?: number) => Type |
|
||||
|
|
||||
/** |
|
||||
* Generate unique ID with custom random generator and alphabet. |
|
||||
* |
|
||||
* Alphabet must contain 256 symbols or less. Otherwise, the generator |
|
||||
* will not be secure. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { customRandom } from 'nanoid/format' |
|
||||
* |
|
||||
* const nanoid = customRandom('abcdef', 5, size => { |
|
||||
* const random = [] |
|
||||
* for (let i = 0; i < size; i++) { |
|
||||
* random.push(randomByte()) |
|
||||
* } |
|
||||
* return random |
|
||||
* }) |
|
||||
* |
|
||||
* nanoid() //=> "fbaef"
|
|
||||
* ```
|
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate a random string. |
|
||||
* @param size Size of the random string. |
|
||||
* @param random A random bytes generator. |
|
||||
* @typeparam Type The ID type to replace `string` with some opaque type. |
|
||||
* @returns A random string generator. |
|
||||
*/ |
|
||||
export function customRandom<Type extends string>( |
|
||||
alphabet: string, |
|
||||
size: number, |
|
||||
random: (bytes: number) => Uint8Array |
|
||||
): () => Type |
|
||||
|
|
||||
/** |
|
||||
* URL safe symbols. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { urlAlphabet } from 'nanoid' |
|
||||
* const nanoid = customAlphabet(urlAlphabet, 10) |
|
||||
* nanoid() //=> "Uakgb_J5m9"
|
|
||||
* ```
|
|
||||
*/ |
|
||||
export const urlAlphabet: string |
|
||||
|
|
||||
/** |
|
||||
* Generate an array of random bytes collected from hardware noise. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { customRandom, random } from 'nanoid' |
|
||||
* const nanoid = customRandom("abcdef", 5, random) |
|
||||
* ```
|
|
||||
* |
|
||||
* @param bytes Size of the array. |
|
||||
* @returns An array of random bytes. |
|
||||
*/ |
|
||||
export function random(bytes: number): Uint8Array |
|
||||
@ -1,47 +0,0 @@ |
|||||
import { webcrypto as crypto } from 'node:crypto' |
|
||||
import { urlAlphabet as scopedUrlAlphabet } from './url-alphabet/index.js' |
|
||||
export { urlAlphabet } from './url-alphabet/index.js' |
|
||||
const POOL_SIZE_MULTIPLIER = 128 |
|
||||
let pool, poolOffset |
|
||||
function fillPool(bytes) { |
|
||||
if (!pool || pool.length < bytes) { |
|
||||
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER) |
|
||||
crypto.getRandomValues(pool) |
|
||||
poolOffset = 0 |
|
||||
} else if (poolOffset + bytes > pool.length) { |
|
||||
crypto.getRandomValues(pool) |
|
||||
poolOffset = 0 |
|
||||
} |
|
||||
poolOffset += bytes |
|
||||
} |
|
||||
export function random(bytes) { |
|
||||
fillPool((bytes |= 0)) |
|
||||
return pool.subarray(poolOffset - bytes, poolOffset) |
|
||||
} |
|
||||
export function customRandom(alphabet, defaultSize, getRandom) { |
|
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
|
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
return (size = defaultSize) => { |
|
||||
if (!size) return '' |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = getRandom(step) |
|
||||
let i = step |
|
||||
while (i--) { |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length >= size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
export function customAlphabet(alphabet, size = 21) { |
|
||||
return customRandom(alphabet, size, random) |
|
||||
} |
|
||||
export function nanoid(size = 21) { |
|
||||
fillPool((size |= 0)) |
|
||||
let id = '' |
|
||||
for (let i = poolOffset - size; i < poolOffset; i++) { |
|
||||
id += scopedUrlAlphabet[pool[i] & 63] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
@ -1 +0,0 @@ |
|||||
let a="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";export let nanoid=(e=21)=>{let t="",r=crypto.getRandomValues(new Uint8Array(e));for(let n=0;n<e;n++)t+=a[63&r[n]];return t}; |
|
||||
@ -1,48 +0,0 @@ |
|||||
/** |
|
||||
* By default, Nano ID uses hardware random bytes generation for security |
|
||||
* and low collision probability. If you are not so concerned with security, |
|
||||
* you can use it for environments without hardware random generators. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { nanoid } from 'nanoid/non-secure' |
|
||||
* const id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqLJ"
|
|
||||
* ```
|
|
||||
* |
|
||||
* @module |
|
||||
*/ |
|
||||
|
|
||||
/** |
|
||||
* Generate URL-friendly unique ID. This method uses the non-secure |
|
||||
* predictable random generator with bigger collision probability. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { nanoid } from 'nanoid/non-secure' |
|
||||
* model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL"
|
|
||||
* ```
|
|
||||
* |
|
||||
* @param size Size of the ID. The default size is 21. |
|
||||
* @typeparam Type The ID type to replace `string` with some opaque type. |
|
||||
* @returns A random string. |
|
||||
*/ |
|
||||
export function nanoid<Type extends string>(size?: number): Type |
|
||||
|
|
||||
/** |
|
||||
* Generate a unique ID based on a custom alphabet. |
|
||||
* This method uses the non-secure predictable random generator |
|
||||
* with bigger collision probability. |
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate the ID. |
|
||||
* @param defaultSize Size of the ID. The default size is 21. |
|
||||
* @typeparam Type The ID type to replace `string` with some opaque type. |
|
||||
* @returns A random string generator. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { customAlphabet } from 'nanoid/non-secure' |
|
||||
* const nanoid = customAlphabet('0123456789абвгдеё', 5) |
|
||||
* model.id = nanoid() //=> "8ё56а"
|
|
||||
* ```
|
|
||||
*/ |
|
||||
export function customAlphabet<Type extends string>( |
|
||||
alphabet: string, |
|
||||
defaultSize?: number |
|
||||
): (size?: number) => Type |
|
||||
@ -1,21 +0,0 @@ |
|||||
/* @ts-self-types="./index.d.ts" */ |
|
||||
let urlAlphabet = |
|
||||
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
|
||||
export let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
return (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
let i = size | 0 |
|
||||
while (i--) { |
|
||||
id += alphabet[(Math.random() * alphabet.length) | 0] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
} |
|
||||
export let nanoid = (size = 21) => { |
|
||||
let id = '' |
|
||||
let i = size | 0 |
|
||||
while (i--) { |
|
||||
id += urlAlphabet[(Math.random() * 64) | 0] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
@ -1,43 +0,0 @@ |
|||||
{ |
|
||||
"name": "nanoid", |
|
||||
"version": "5.1.6", |
|
||||
"description": "A tiny (118 bytes), secure URL-friendly unique string ID generator", |
|
||||
"keywords": [ |
|
||||
"uuid", |
|
||||
"random", |
|
||||
"id", |
|
||||
"url" |
|
||||
], |
|
||||
"type": "module", |
|
||||
"engines": { |
|
||||
"node": "^18 || >=20" |
|
||||
}, |
|
||||
"funding": [ |
|
||||
{ |
|
||||
"type": "github", |
|
||||
"url": "https://github.com/sponsors/ai" |
|
||||
} |
|
||||
], |
|
||||
"author": "Andrey Sitnik <andrey@sitnik.ru>", |
|
||||
"license": "MIT", |
|
||||
"repository": "ai/nanoid", |
|
||||
"exports": { |
|
||||
".": { |
|
||||
"types": "./index.d.ts", |
|
||||
"browser": "./index.browser.js", |
|
||||
"react-native": "./index.browser.js", |
|
||||
"default": "./index.js" |
|
||||
}, |
|
||||
"./non-secure": "./non-secure/index.js", |
|
||||
"./package.json": "./package.json" |
|
||||
}, |
|
||||
"browser": { |
|
||||
"./index.js": "./index.browser.js" |
|
||||
}, |
|
||||
"react-native": { |
|
||||
"./index.js": "./index.browser.js" |
|
||||
}, |
|
||||
"bin": "./bin/nanoid.js", |
|
||||
"sideEffects": false, |
|
||||
"types": "./index.d.ts" |
|
||||
} |
|
||||
@ -1,2 +0,0 @@ |
|||||
export const urlAlphabet = |
|
||||
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
|
||||
@ -1,69 +0,0 @@ |
|||||
let random = async bytes => crypto.getRandomValues(new Uint8Array(bytes)) |
|
||||
|
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
// First, a bitmask is necessary to generate the ID. The bitmask makes bytes |
|
||||
// values closer to the alphabet size. The bitmask calculates the closest |
|
||||
// `2^31 - 1` number, which exceeds the alphabet size. |
|
||||
// For example, the bitmask for the alphabet size 30 is 31 (00011111). |
|
||||
// `Math.clz32` is not used, because it is not available in browsers. |
|
||||
let mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1 |
|
||||
// Though, the bitmask solution is not perfect since the bytes exceeding |
|
||||
// the alphabet size are refused. Therefore, to reliably generate the ID, |
|
||||
// the random bytes redundancy has to be satisfied. |
|
||||
|
|
||||
// Note: every hardware random generator call is performance expensive, |
|
||||
// because the system call for entropy collection takes a lot of time. |
|
||||
// So, to avoid additional system calls, extra bytes are requested in advance. |
|
||||
|
|
||||
// Next, a step determines how many random bytes to generate. |
|
||||
// The number of random bytes gets decided upon the ID size, mask, |
|
||||
// alphabet size, and magic number 1.6 (using 1.6 peaks at performance |
|
||||
// according to benchmarks). |
|
||||
|
|
||||
// `-~f => Math.ceil(f)` if f is a float |
|
||||
// `-~i => i + 1` if i is an integer |
|
||||
let step = -~((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
|
|
||||
return async (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = crypto.getRandomValues(new Uint8Array(step)) |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
let i = step | 0 |
|
||||
while (i--) { |
|
||||
// Adding `|| ''` refuses a random byte that exceeds the alphabet size. |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length === size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
let nanoid = async (size = 21) => { |
|
||||
let id = '' |
|
||||
let bytes = crypto.getRandomValues(new Uint8Array((size |= 0))) |
|
||||
|
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
while (size--) { |
|
||||
// It is incorrect to use bytes exceeding the alphabet size. |
|
||||
// The following mask reduces the random byte in the 0-255 value |
|
||||
// range to the 0-63 value range. Therefore, adding hacks, such |
|
||||
// as empty string fallback or magic numbers, is unneccessary because |
|
||||
// the bitmask trims bytes down to the alphabet size. |
|
||||
let byte = bytes[size] & 63 |
|
||||
if (byte < 36) { |
|
||||
// `0-9a-z` |
|
||||
id += byte.toString(36) |
|
||||
} else if (byte < 62) { |
|
||||
// `A-Z` |
|
||||
id += (byte - 26).toString(36).toUpperCase() |
|
||||
} else if (byte < 63) { |
|
||||
id += '_' |
|
||||
} else { |
|
||||
id += '-' |
|
||||
} |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
|
|
||||
module.exports = { nanoid, customAlphabet, random } |
|
||||
@ -1,34 +0,0 @@ |
|||||
let random = async bytes => crypto.getRandomValues(new Uint8Array(bytes)) |
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
let mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1 |
|
||||
let step = -~((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
return async (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = crypto.getRandomValues(new Uint8Array(step)) |
|
||||
let i = step | 0 |
|
||||
while (i--) { |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length === size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
let nanoid = async (size = 21) => { |
|
||||
let id = '' |
|
||||
let bytes = crypto.getRandomValues(new Uint8Array((size |= 0))) |
|
||||
while (size--) { |
|
||||
let byte = bytes[size] & 63 |
|
||||
if (byte < 36) { |
|
||||
id += byte.toString(36) |
|
||||
} else if (byte < 62) { |
|
||||
id += (byte - 26).toString(36).toUpperCase() |
|
||||
} else if (byte < 63) { |
|
||||
id += '_' |
|
||||
} else { |
|
||||
id += '-' |
|
||||
} |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
export { nanoid, customAlphabet, random } |
|
||||
@ -1,71 +0,0 @@ |
|||||
let crypto = require('crypto') |
|
||||
|
|
||||
let { urlAlphabet } = require('../url-alphabet/index.cjs') |
|
||||
|
|
||||
// `crypto.randomFill()` is a little faster than `crypto.randomBytes()`, |
|
||||
// because it is possible to use in combination with `Buffer.allocUnsafe()`. |
|
||||
let random = bytes => |
|
||||
new Promise((resolve, reject) => { |
|
||||
// `Buffer.allocUnsafe()` is faster because it doesn’t flush the memory. |
|
||||
// Memory flushing is unnecessary since the buffer allocation itself resets |
|
||||
// the memory with the new bytes. |
|
||||
crypto.randomFill(Buffer.allocUnsafe(bytes), (err, buf) => { |
|
||||
if (err) { |
|
||||
reject(err) |
|
||||
} else { |
|
||||
resolve(buf) |
|
||||
} |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
// First, a bitmask is necessary to generate the ID. The bitmask makes bytes |
|
||||
// values closer to the alphabet size. The bitmask calculates the closest |
|
||||
// `2^31 - 1` number, which exceeds the alphabet size. |
|
||||
// For example, the bitmask for the alphabet size 30 is 31 (00011111). |
|
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
|
||||
// Though, the bitmask solution is not perfect since the bytes exceeding |
|
||||
// the alphabet size are refused. Therefore, to reliably generate the ID, |
|
||||
// the random bytes redundancy has to be satisfied. |
|
||||
|
|
||||
// Note: every hardware random generator call is performance expensive, |
|
||||
// because the system call for entropy collection takes a lot of time. |
|
||||
// So, to avoid additional system calls, extra bytes are requested in advance. |
|
||||
|
|
||||
// Next, a step determines how many random bytes to generate. |
|
||||
// The number of random bytes gets decided upon the ID size, mask, |
|
||||
// alphabet size, and magic number 1.6 (using 1.6 peaks at performance |
|
||||
// according to benchmarks). |
|
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
|
|
||||
let tick = (id, size = defaultSize) => |
|
||||
random(step).then(bytes => { |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
let i = step |
|
||||
while (i--) { |
|
||||
// Adding `|| ''` refuses a random byte that exceeds the alphabet size. |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length >= size) return id |
|
||||
} |
|
||||
return tick(id, size) |
|
||||
}) |
|
||||
|
|
||||
return size => tick('', size) |
|
||||
} |
|
||||
|
|
||||
let nanoid = (size = 21) => |
|
||||
random((size |= 0)).then(bytes => { |
|
||||
let id = '' |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
while (size--) { |
|
||||
// It is incorrect to use bytes exceeding the alphabet size. |
|
||||
// The following mask reduces the random byte in the 0-255 value |
|
||||
// range to the 0-63 value range. Therefore, adding hacks, such |
|
||||
// as empty string fallback or magic numbers, is unneccessary because |
|
||||
// the bitmask trims bytes down to the alphabet size. |
|
||||
id += urlAlphabet[bytes[size] & 63] |
|
||||
} |
|
||||
return id |
|
||||
}) |
|
||||
|
|
||||
module.exports = { nanoid, customAlphabet, random } |
|
||||
@ -1,56 +0,0 @@ |
|||||
/** |
|
||||
* Generate secure URL-friendly unique ID. The non-blocking version. |
|
||||
* |
|
||||
* By default, the ID will have 21 symbols to have a collision probability |
|
||||
* similar to UUID v4. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { nanoid } from 'nanoid/async' |
|
||||
* nanoid().then(id => { |
|
||||
* model.id = id |
|
||||
* }) |
|
||||
* ```
|
|
||||
* |
|
||||
* @param size Size of the ID. The default size is 21. |
|
||||
* @returns A promise with a random string. |
|
||||
*/ |
|
||||
export function nanoid(size?: number): Promise<string> |
|
||||
|
|
||||
/** |
|
||||
* A low-level function. |
|
||||
* Generate secure unique ID with custom alphabet. The non-blocking version. |
|
||||
* |
|
||||
* Alphabet must contain 256 symbols or less. Otherwise, the generator |
|
||||
* will not be secure. |
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate the ID. |
|
||||
* @param defaultSize Size of the ID. The default size is 21. |
|
||||
* @returns A function that returns a promise with a random string. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { customAlphabet } from 'nanoid/async' |
|
||||
* const nanoid = customAlphabet('0123456789абвгдеё', 5) |
|
||||
* nanoid().then(id => { |
|
||||
* model.id = id //=> "8ё56а"
|
|
||||
* }) |
|
||||
* ```
|
|
||||
*/ |
|
||||
export function customAlphabet( |
|
||||
alphabet: string, |
|
||||
defaultSize?: number |
|
||||
): (size?: number) => Promise<string> |
|
||||
|
|
||||
/** |
|
||||
* Generate an array of random bytes collected from hardware noise. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { random } from 'nanoid/async' |
|
||||
* random(5).then(bytes => { |
|
||||
* bytes //=> [10, 67, 212, 67, 89]
|
|
||||
* }) |
|
||||
* ```
|
|
||||
* |
|
||||
* @param bytes Size of the array. |
|
||||
* @returns A promise with a random bytes array. |
|
||||
*/ |
|
||||
export function random(bytes: number): Promise<Uint8Array> |
|
||||
@ -1,35 +0,0 @@ |
|||||
import crypto from 'crypto' |
|
||||
import { urlAlphabet } from '../url-alphabet/index.js' |
|
||||
let random = bytes => |
|
||||
new Promise((resolve, reject) => { |
|
||||
crypto.randomFill(Buffer.allocUnsafe(bytes), (err, buf) => { |
|
||||
if (err) { |
|
||||
reject(err) |
|
||||
} else { |
|
||||
resolve(buf) |
|
||||
} |
|
||||
}) |
|
||||
}) |
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
|
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
let tick = (id, size = defaultSize) => |
|
||||
random(step).then(bytes => { |
|
||||
let i = step |
|
||||
while (i--) { |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length >= size) return id |
|
||||
} |
|
||||
return tick(id, size) |
|
||||
}) |
|
||||
return size => tick('', size) |
|
||||
} |
|
||||
let nanoid = (size = 21) => |
|
||||
random((size |= 0)).then(bytes => { |
|
||||
let id = '' |
|
||||
while (size--) { |
|
||||
id += urlAlphabet[bytes[size] & 63] |
|
||||
} |
|
||||
return id |
|
||||
}) |
|
||||
export { nanoid, customAlphabet, random } |
|
||||
@ -1,26 +0,0 @@ |
|||||
import { getRandomBytesAsync } from 'expo-random' |
|
||||
import { urlAlphabet } from '../url-alphabet/index.js' |
|
||||
let random = getRandomBytesAsync |
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
|
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
let tick = (id, size = defaultSize) => |
|
||||
random(step).then(bytes => { |
|
||||
let i = step |
|
||||
while (i--) { |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length >= size) return id |
|
||||
} |
|
||||
return tick(id, size) |
|
||||
}) |
|
||||
return size => tick('', size) |
|
||||
} |
|
||||
let nanoid = (size = 21) => |
|
||||
random((size |= 0)).then(bytes => { |
|
||||
let id = '' |
|
||||
while (size--) { |
|
||||
id += urlAlphabet[bytes[size] & 63] |
|
||||
} |
|
||||
return id |
|
||||
}) |
|
||||
export { nanoid, customAlphabet, random } |
|
||||
@ -1,12 +0,0 @@ |
|||||
{ |
|
||||
"type": "module", |
|
||||
"main": "index.cjs", |
|
||||
"module": "index.js", |
|
||||
"react-native": { |
|
||||
"./index.js": "./index.native.js" |
|
||||
}, |
|
||||
"browser": { |
|
||||
"./index.js": "./index.browser.js", |
|
||||
"./index.cjs": "./index.browser.cjs" |
|
||||
} |
|
||||
} |
|
||||
@ -1,55 +0,0 @@ |
|||||
#!/usr/bin/env node |
|
||||
|
|
||||
let { nanoid, customAlphabet } = require('..') |
|
||||
|
|
||||
function print(msg) { |
|
||||
process.stdout.write(msg + '\n') |
|
||||
} |
|
||||
|
|
||||
function error(msg) { |
|
||||
process.stderr.write(msg + '\n') |
|
||||
process.exit(1) |
|
||||
} |
|
||||
|
|
||||
if (process.argv.includes('--help') || process.argv.includes('-h')) { |
|
||||
print(` |
|
||||
Usage |
|
||||
$ nanoid [options] |
|
||||
|
|
||||
Options |
|
||||
-s, --size Generated ID size |
|
||||
-a, --alphabet Alphabet to use |
|
||||
-h, --help Show this help |
|
||||
|
|
||||
Examples |
|
||||
$ nanoid --s 15 |
|
||||
S9sBF77U6sDB8Yg |
|
||||
|
|
||||
$ nanoid --size 10 --alphabet abc |
|
||||
bcabababca`) |
|
||||
process.exit() |
|
||||
} |
|
||||
|
|
||||
let alphabet, size |
|
||||
for (let i = 2; i < process.argv.length; i++) { |
|
||||
let arg = process.argv[i] |
|
||||
if (arg === '--size' || arg === '-s') { |
|
||||
size = Number(process.argv[i + 1]) |
|
||||
i += 1 |
|
||||
if (Number.isNaN(size) || size <= 0) { |
|
||||
error('Size must be positive integer') |
|
||||
} |
|
||||
} else if (arg === '--alphabet' || arg === '-a') { |
|
||||
alphabet = process.argv[i + 1] |
|
||||
i += 1 |
|
||||
} else { |
|
||||
error('Unknown argument ' + arg) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (alphabet) { |
|
||||
let customNanoid = customAlphabet(alphabet, size) |
|
||||
print(customNanoid()) |
|
||||
} else { |
|
||||
print(nanoid(size)) |
|
||||
} |
|
||||
@ -1,72 +0,0 @@ |
|||||
// This file replaces `index.js` in bundlers like webpack or Rollup, |
|
||||
// according to `browser` config in `package.json`. |
|
||||
|
|
||||
let { urlAlphabet } = require('./url-alphabet/index.cjs') |
|
||||
|
|
||||
let random = bytes => crypto.getRandomValues(new Uint8Array(bytes)) |
|
||||
|
|
||||
let customRandom = (alphabet, defaultSize, getRandom) => { |
|
||||
// First, a bitmask is necessary to generate the ID. The bitmask makes bytes |
|
||||
// values closer to the alphabet size. The bitmask calculates the closest |
|
||||
// `2^31 - 1` number, which exceeds the alphabet size. |
|
||||
// For example, the bitmask for the alphabet size 30 is 31 (00011111). |
|
||||
// `Math.clz32` is not used, because it is not available in browsers. |
|
||||
let mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1 |
|
||||
// Though, the bitmask solution is not perfect since the bytes exceeding |
|
||||
// the alphabet size are refused. Therefore, to reliably generate the ID, |
|
||||
// the random bytes redundancy has to be satisfied. |
|
||||
|
|
||||
// Note: every hardware random generator call is performance expensive, |
|
||||
// because the system call for entropy collection takes a lot of time. |
|
||||
// So, to avoid additional system calls, extra bytes are requested in advance. |
|
||||
|
|
||||
// Next, a step determines how many random bytes to generate. |
|
||||
// The number of random bytes gets decided upon the ID size, mask, |
|
||||
// alphabet size, and magic number 1.6 (using 1.6 peaks at performance |
|
||||
// according to benchmarks). |
|
||||
|
|
||||
// `-~f => Math.ceil(f)` if f is a float |
|
||||
// `-~i => i + 1` if i is an integer |
|
||||
let step = -~((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
|
|
||||
return (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = getRandom(step) |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
let j = step | 0 |
|
||||
while (j--) { |
|
||||
// Adding `|| ''` refuses a random byte that exceeds the alphabet size. |
|
||||
id += alphabet[bytes[j] & mask] || '' |
|
||||
if (id.length === size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
let customAlphabet = (alphabet, size = 21) => |
|
||||
customRandom(alphabet, size, random) |
|
||||
|
|
||||
let nanoid = (size = 21) => |
|
||||
crypto.getRandomValues(new Uint8Array(size)).reduce((id, byte) => { |
|
||||
// It is incorrect to use bytes exceeding the alphabet size. |
|
||||
// The following mask reduces the random byte in the 0-255 value |
|
||||
// range to the 0-63 value range. Therefore, adding hacks, such |
|
||||
// as empty string fallback or magic numbers, is unneccessary because |
|
||||
// the bitmask trims bytes down to the alphabet size. |
|
||||
byte &= 63 |
|
||||
if (byte < 36) { |
|
||||
// `0-9a-z` |
|
||||
id += byte.toString(36) |
|
||||
} else if (byte < 62) { |
|
||||
// `A-Z` |
|
||||
id += (byte - 26).toString(36).toUpperCase() |
|
||||
} else if (byte > 62) { |
|
||||
id += '-' |
|
||||
} else { |
|
||||
id += '_' |
|
||||
} |
|
||||
return id |
|
||||
}, '') |
|
||||
|
|
||||
module.exports = { nanoid, customAlphabet, customRandom, urlAlphabet, random } |
|
||||
@ -1,85 +0,0 @@ |
|||||
let crypto = require('crypto') |
|
||||
|
|
||||
let { urlAlphabet } = require('./url-alphabet/index.cjs') |
|
||||
|
|
||||
// It is best to make fewer, larger requests to the crypto module to |
|
||||
// avoid system call overhead. So, random numbers are generated in a |
|
||||
// pool. The pool is a Buffer that is larger than the initial random |
|
||||
// request size by this multiplier. The pool is enlarged if subsequent |
|
||||
// requests exceed the maximum buffer size. |
|
||||
const POOL_SIZE_MULTIPLIER = 128 |
|
||||
let pool, poolOffset |
|
||||
|
|
||||
let fillPool = bytes => { |
|
||||
if (!pool || pool.length < bytes) { |
|
||||
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER) |
|
||||
crypto.randomFillSync(pool) |
|
||||
poolOffset = 0 |
|
||||
} else if (poolOffset + bytes > pool.length) { |
|
||||
crypto.randomFillSync(pool) |
|
||||
poolOffset = 0 |
|
||||
} |
|
||||
poolOffset += bytes |
|
||||
} |
|
||||
|
|
||||
let random = bytes => { |
|
||||
// `|=` convert `bytes` to number to prevent `valueOf` abusing and pool pollution |
|
||||
fillPool((bytes |= 0)) |
|
||||
return pool.subarray(poolOffset - bytes, poolOffset) |
|
||||
} |
|
||||
|
|
||||
let customRandom = (alphabet, defaultSize, getRandom) => { |
|
||||
// First, a bitmask is necessary to generate the ID. The bitmask makes bytes |
|
||||
// values closer to the alphabet size. The bitmask calculates the closest |
|
||||
// `2^31 - 1` number, which exceeds the alphabet size. |
|
||||
// For example, the bitmask for the alphabet size 30 is 31 (00011111). |
|
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
|
||||
// Though, the bitmask solution is not perfect since the bytes exceeding |
|
||||
// the alphabet size are refused. Therefore, to reliably generate the ID, |
|
||||
// the random bytes redundancy has to be satisfied. |
|
||||
|
|
||||
// Note: every hardware random generator call is performance expensive, |
|
||||
// because the system call for entropy collection takes a lot of time. |
|
||||
// So, to avoid additional system calls, extra bytes are requested in advance. |
|
||||
|
|
||||
// Next, a step determines how many random bytes to generate. |
|
||||
// The number of random bytes gets decided upon the ID size, mask, |
|
||||
// alphabet size, and magic number 1.6 (using 1.6 peaks at performance |
|
||||
// according to benchmarks). |
|
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
|
|
||||
return (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = getRandom(step) |
|
||||
// A compact alternative for `for (let i = 0; i < step; i++)`. |
|
||||
let i = step |
|
||||
while (i--) { |
|
||||
// Adding `|| ''` refuses a random byte that exceeds the alphabet size. |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length === size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
let customAlphabet = (alphabet, size = 21) => |
|
||||
customRandom(alphabet, size, random) |
|
||||
|
|
||||
let nanoid = (size = 21) => { |
|
||||
// `|=` convert `size` to number to prevent `valueOf` abusing and pool pollution |
|
||||
fillPool((size |= 0)) |
|
||||
let id = '' |
|
||||
// We are reading directly from the random pool to avoid creating new array |
|
||||
for (let i = poolOffset - size; i < poolOffset; i++) { |
|
||||
// It is incorrect to use bytes exceeding the alphabet size. |
|
||||
// The following mask reduces the random byte in the 0-255 value |
|
||||
// range to the 0-63 value range. Therefore, adding hacks, such |
|
||||
// as empty string fallback or magic numbers, is unneccessary because |
|
||||
// the bitmask trims bytes down to the alphabet size. |
|
||||
id += urlAlphabet[pool[i] & 63] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
|
|
||||
module.exports = { nanoid, customAlphabet, customRandom, urlAlphabet, random } |
|
||||
@ -1,91 +0,0 @@ |
|||||
/** |
|
||||
* Generate secure URL-friendly unique ID. |
|
||||
* |
|
||||
* By default, the ID will have 21 symbols to have a collision probability |
|
||||
* similar to UUID v4. |
|
||||
* |
|
||||
* ```js |
|
||||
* import { nanoid } from 'nanoid' |
|
||||
* model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL" |
|
||||
* ``` |
|
||||
* |
|
||||
* @param size Size of the ID. The default size is 21. |
|
||||
* @returns A random string. |
|
||||
*/ |
|
||||
export function nanoid(size?: number): string |
|
||||
|
|
||||
/** |
|
||||
* Generate secure unique ID with custom alphabet. |
|
||||
* |
|
||||
* Alphabet must contain 256 symbols or less. Otherwise, the generator |
|
||||
* will not be secure. |
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate the ID. |
|
||||
* @param defaultSize Size of the ID. The default size is 21. |
|
||||
* @returns A random string generator. |
|
||||
* |
|
||||
* ```js |
|
||||
* const { customAlphabet } = require('nanoid') |
|
||||
* const nanoid = customAlphabet('0123456789абвгдеё', 5) |
|
||||
* nanoid() //=> "8ё56а" |
|
||||
* ``` |
|
||||
*/ |
|
||||
export function customAlphabet( |
|
||||
alphabet: string, |
|
||||
defaultSize?: number |
|
||||
): (size?: number) => string |
|
||||
|
|
||||
/** |
|
||||
* Generate unique ID with custom random generator and alphabet. |
|
||||
* |
|
||||
* Alphabet must contain 256 symbols or less. Otherwise, the generator |
|
||||
* will not be secure. |
|
||||
* |
|
||||
* ```js |
|
||||
* import { customRandom } from 'nanoid/format' |
|
||||
* |
|
||||
* const nanoid = customRandom('abcdef', 5, size => { |
|
||||
* const random = [] |
|
||||
* for (let i = 0; i < size; i++) { |
|
||||
* random.push(randomByte()) |
|
||||
* } |
|
||||
* return random |
|
||||
* }) |
|
||||
* |
|
||||
* nanoid() //=> "fbaef" |
|
||||
* ``` |
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate a random string. |
|
||||
* @param size Size of the random string. |
|
||||
* @param random A random bytes generator. |
|
||||
* @returns A random string generator. |
|
||||
*/ |
|
||||
export function customRandom( |
|
||||
alphabet: string, |
|
||||
size: number, |
|
||||
random: (bytes: number) => Uint8Array |
|
||||
): () => string |
|
||||
|
|
||||
/** |
|
||||
* URL safe symbols. |
|
||||
* |
|
||||
* ```js |
|
||||
* import { urlAlphabet } from 'nanoid' |
|
||||
* const nanoid = customAlphabet(urlAlphabet, 10) |
|
||||
* nanoid() //=> "Uakgb_J5m9" |
|
||||
* ``` |
|
||||
*/ |
|
||||
export const urlAlphabet: string |
|
||||
|
|
||||
/** |
|
||||
* Generate an array of random bytes collected from hardware noise. |
|
||||
* |
|
||||
* ```js |
|
||||
* import { customRandom, random } from 'nanoid' |
|
||||
* const nanoid = customRandom("abcdef", 5, random) |
|
||||
* ``` |
|
||||
* |
|
||||
* @param bytes Size of the array. |
|
||||
* @returns An array of random bytes. |
|
||||
*/ |
|
||||
export function random(bytes: number): Uint8Array |
|
||||
@ -1,45 +1,47 @@ |
|||||
import crypto from 'crypto' |
|
||||
import { urlAlphabet } from './url-alphabet/index.js' |
|
||||
|
import { webcrypto as crypto } from 'node:crypto' |
||||
|
import { urlAlphabet as scopedUrlAlphabet } from './url-alphabet/index.js' |
||||
|
export { urlAlphabet } from './url-alphabet/index.js' |
||||
const POOL_SIZE_MULTIPLIER = 128 |
const POOL_SIZE_MULTIPLIER = 128 |
||||
let pool, poolOffset |
let pool, poolOffset |
||||
let fillPool = bytes => { |
|
||||
|
function fillPool(bytes) { |
||||
if (!pool || pool.length < bytes) { |
if (!pool || pool.length < bytes) { |
||||
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER) |
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER) |
||||
crypto.randomFillSync(pool) |
|
||||
|
crypto.getRandomValues(pool) |
||||
poolOffset = 0 |
poolOffset = 0 |
||||
} else if (poolOffset + bytes > pool.length) { |
} else if (poolOffset + bytes > pool.length) { |
||||
crypto.randomFillSync(pool) |
|
||||
|
crypto.getRandomValues(pool) |
||||
poolOffset = 0 |
poolOffset = 0 |
||||
} |
} |
||||
poolOffset += bytes |
poolOffset += bytes |
||||
} |
} |
||||
let random = bytes => { |
|
||||
|
export function random(bytes) { |
||||
fillPool((bytes |= 0)) |
fillPool((bytes |= 0)) |
||||
return pool.subarray(poolOffset - bytes, poolOffset) |
return pool.subarray(poolOffset - bytes, poolOffset) |
||||
} |
} |
||||
let customRandom = (alphabet, defaultSize, getRandom) => { |
|
||||
|
export function customRandom(alphabet, defaultSize, getRandom) { |
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
||||
return (size = defaultSize) => { |
return (size = defaultSize) => { |
||||
|
if (!size) return '' |
||||
let id = '' |
let id = '' |
||||
while (true) { |
while (true) { |
||||
let bytes = getRandom(step) |
let bytes = getRandom(step) |
||||
let i = step |
let i = step |
||||
while (i--) { |
while (i--) { |
||||
id += alphabet[bytes[i] & mask] || '' |
id += alphabet[bytes[i] & mask] || '' |
||||
if (id.length === size) return id |
|
||||
|
if (id.length >= size) return id |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
let customAlphabet = (alphabet, size = 21) => |
|
||||
customRandom(alphabet, size, random) |
|
||||
let nanoid = (size = 21) => { |
|
||||
|
export function customAlphabet(alphabet, size = 21) { |
||||
|
return customRandom(alphabet, size, random) |
||||
|
} |
||||
|
export function nanoid(size = 21) { |
||||
fillPool((size |= 0)) |
fillPool((size |= 0)) |
||||
let id = '' |
let id = '' |
||||
for (let i = poolOffset - size; i < poolOffset; i++) { |
for (let i = poolOffset - size; i < poolOffset; i++) { |
||||
id += urlAlphabet[pool[i] & 63] |
|
||||
|
id += scopedUrlAlphabet[pool[i] & 63] |
||||
} |
} |
||||
return id |
return id |
||||
} |
} |
||||
export { nanoid, customAlphabet, customRandom, urlAlphabet, random } |
|
||||
@ -1 +1 @@ |
|||||
export let nanoid=(t=21)=>crypto.getRandomValues(new Uint8Array(t)).reduce(((t,e)=>t+=(e&=63)<36?e.toString(36):e<62?(e-26).toString(36).toUpperCase():e<63?"_":"-"),""); |
|
||||
|
let a="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";export let nanoid=(e=21)=>{let t="",r=crypto.getRandomValues(new Uint8Array(e));for(let n=0;n<e;n++)t+=a[63&r[n]];return t}; |
||||
@ -1,34 +0,0 @@ |
|||||
// This alphabet uses `A-Za-z0-9_-` symbols. |
|
||||
// The order of characters is optimized for better gzip and brotli compression. |
|
||||
// References to the same file (works both for gzip and brotli): |
|
||||
// `'use`, `andom`, and `rict'` |
|
||||
// References to the brotli default dictionary: |
|
||||
// `-26T`, `1983`, `40px`, `75px`, `bush`, `jack`, `mind`, `very`, and `wolf` |
|
||||
let urlAlphabet = |
|
||||
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
|
||||
|
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
return (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
let i = size | 0 |
|
||||
while (i--) { |
|
||||
// `| 0` is more compact and faster than `Math.floor()`. |
|
||||
id += alphabet[(Math.random() * alphabet.length) | 0] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
let nanoid = (size = 21) => { |
|
||||
let id = '' |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
let i = size | 0 |
|
||||
while (i--) { |
|
||||
// `| 0` is more compact and faster than `Math.floor()`. |
|
||||
id += urlAlphabet[(Math.random() * 64) | 0] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
|
|
||||
module.exports = { nanoid, customAlphabet } |
|
||||
@ -1,6 +0,0 @@ |
|||||
{ |
|
||||
"type": "module", |
|
||||
"main": "index.cjs", |
|
||||
"module": "index.js", |
|
||||
"react-native": "index.js" |
|
||||
} |
|
||||
@ -1,7 +0,0 @@ |
|||||
// This alphabet uses `A-Za-z0-9_-` symbols. |
|
||||
// The order of characters is optimized for better gzip and brotli compression. |
|
||||
// Same as in non-secure/index.js |
|
||||
let urlAlphabet = |
|
||||
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
|
||||
|
|
||||
module.exports = { urlAlphabet } |
|
||||
@ -1,3 +1,2 @@ |
|||||
let urlAlphabet = |
|
||||
|
export const urlAlphabet = |
||||
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
||||
export { urlAlphabet } |
|
||||
@ -1,6 +0,0 @@ |
|||||
{ |
|
||||
"type": "module", |
|
||||
"main": "index.cjs", |
|
||||
"module": "index.js", |
|
||||
"react-native": "index.js" |
|
||||
} |
|
||||
@ -1,69 +1,10 @@ |
|||||
<template> |
<template> |
||||
<div class="app"> |
|
||||
<h2 class="title">Vue路由测试</h2> |
|
||||
<!-- 导航区 --> |
|
||||
<div class="navigate"> |
|
||||
<RouterLink to="/home" active-class="active">首页</RouterLink> |
|
||||
<RouterLink to="/news" active-class="active">新闻</RouterLink> |
|
||||
<RouterLink to="/about" active-class="active">关于</RouterLink> |
|
||||
</div> |
|
||||
<!-- 展示区 --> |
|
||||
<div class="main-content"> |
|
||||
<RouterView></RouterView> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
<Count/> |
||||
|
<br> |
||||
|
<LoveTalk/> |
||||
</template> |
</template> |
||||
|
|
||||
<script lang="ts" setup name="App"> |
|
||||
import { RouterLink, RouterView } from 'vue-router' |
|
||||
|
<script setup lang="ts" name="App"> |
||||
|
import Count from './components/Count.vue' |
||||
|
import LoveTalk from './components/LoveTalk.vue' |
||||
</script> |
</script> |
||||
|
|
||||
<style> |
|
||||
/* 全局样式重置(可选) */ |
|
||||
* { |
|
||||
margin: 0; |
|
||||
padding: 0; |
|
||||
box-sizing: border-box; |
|
||||
} |
|
||||
|
|
||||
/* 页面容器 */ |
|
||||
.app { |
|
||||
width: 800px; |
|
||||
margin: 0 auto; |
|
||||
} |
|
||||
|
|
||||
/* 标题样式 */ |
|
||||
.title { |
|
||||
background-color: #888; |
|
||||
color: #fff; |
|
||||
padding: 10px 0; |
|
||||
text-align: center; |
|
||||
} |
|
||||
|
|
||||
/* 导航区样式 */ |
|
||||
.navigate { |
|
||||
display: flex; |
|
||||
margin: 20px 0; |
|
||||
} |
|
||||
|
|
||||
.navigate a { |
|
||||
padding: 6px 12px; |
|
||||
border-radius: 4px; |
|
||||
text-decoration: none; |
|
||||
margin-right: 10px; |
|
||||
color: #333; |
|
||||
} |
|
||||
|
|
||||
/* 激活态样式 */ |
|
||||
.navigate a.active { |
|
||||
background-color: #6a994e; |
|
||||
color: #fff; |
|
||||
} |
|
||||
|
|
||||
/* 内容展示区样式 */ |
|
||||
.main-content { |
|
||||
border: 1px solid #ccc; |
|
||||
min-height: 300px; |
|
||||
padding: 10px; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,47 @@ |
|||||
|
<template> |
||||
|
<div class="count"> |
||||
|
<h2>当前求和为:{{ sum }},放大10倍后:{{ bigSum }}</h2> |
||||
|
<select v-model.number="n"> |
||||
|
<option value="1">1</option> |
||||
|
<option value="2">2</option> |
||||
|
<option value="3">3</option> |
||||
|
</select> |
||||
|
<button @click="add">加</button> |
||||
|
<button @click="minus">减</button> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts" name="Count"> |
||||
|
import { ref,reactive,toRefs } from "vue"; |
||||
|
import {storeToRefs} from 'pinia' |
||||
|
// 引入useCountStore |
||||
|
import {useCountStore} from '@/store/count' |
||||
|
// 使用useCountStore,得到一个专门保存count相关的store |
||||
|
const countStore = useCountStore() |
||||
|
// storeToRefs只会关注sotre中数据,不会对方法进行ref包裹 |
||||
|
const {sum,bigSum} = storeToRefs(countStore) |
||||
|
// console.log('!!!!!',storeToRefs(countStore)) |
||||
|
|
||||
|
// 数据 |
||||
|
let n = ref(1) // 用户选择的数字 |
||||
|
// 方法 |
||||
|
function add(){ |
||||
|
countStore.increment(n.value) |
||||
|
} |
||||
|
function minus(){ |
||||
|
countStore.sum -= n.value |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.count { |
||||
|
background-color: skyblue; |
||||
|
padding: 10px; |
||||
|
border-radius: 10px; |
||||
|
box-shadow: 0 0 10px; |
||||
|
} |
||||
|
select,button { |
||||
|
margin: 0 5px; |
||||
|
height: 25px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,45 @@ |
|||||
|
<template> |
||||
|
<div class="talk"> |
||||
|
<button @click="getLoveTalk">获取一句土味情话</button> |
||||
|
<ul> |
||||
|
<li v-for="talk in talkStore.talkList" :key="talk.id">{{talk.title}}</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts" name="LoveTalk"> |
||||
|
import {reactive} from 'vue' |
||||
|
import axios from "axios"; |
||||
|
import {nanoid} from 'nanoid' |
||||
|
import {useTalkStore} from '@/store/loveTalk' |
||||
|
import { storeToRefs } from "pinia"; |
||||
|
|
||||
|
const talkStore = useTalkStore() |
||||
|
const {talkList} = storeToRefs(talkStore) |
||||
|
|
||||
|
talkStore.$subscribe((mutate,state) => { |
||||
|
console.log('talkStore里面保存的数据发生了变化',mutate,state) |
||||
|
localStorage.setItem('talkList',JSON.stringify(state.talkList)) |
||||
|
}) |
||||
|
|
||||
|
// 方法 |
||||
|
async function getLoveTalk(){ |
||||
|
|
||||
|
talkStore.getATalk() |
||||
|
// 发请求,下面这行的写法是:连续解构赋值+重命名 |
||||
|
// let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') |
||||
|
// 把请求回来的字符串,包装成一个对象 |
||||
|
// let obj = {id:nanoid(),title} |
||||
|
// 放到数组中 |
||||
|
// talkList.unshift(obj) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.talk { |
||||
|
background-color: orange; |
||||
|
padding: 10px; |
||||
|
border-radius: 10px; |
||||
|
box-shadow: 0 0 10px; |
||||
|
} |
||||
|
</style> |
||||
@ -1,34 +0,0 @@ |
|||||
<template> |
|
||||
<div class="news"> |
|
||||
<ul> |
|
||||
<li><a href="#">新闻1</a></li> |
|
||||
<li><a href="#">新闻2</a></li> |
|
||||
<li><a href="#">新闻3</a></li> |
|
||||
<li><a href="#">新闻4</a></li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script lang="ts" setup name="News"> |
|
||||
|
|
||||
|
|
||||
</script> |
|
||||
|
|
||||
<style> |
|
||||
.news { |
|
||||
padding: 0 20px; |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
height: 100px; |
|
||||
} |
|
||||
.news ul { |
|
||||
margin-top: 30px; |
|
||||
list-style: none; |
|
||||
padding-left: 10px; |
|
||||
} |
|
||||
.news li>a{ |
|
||||
text-decoration: none; |
|
||||
color: #000; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,48 +0,0 @@ |
|||||
<template> |
|
||||
<div class="person"> |
|
||||
<h2>当前求和为:{{sum}}</h2> |
|
||||
<button @click="add">点我sum+1</button> |
|
||||
|
|
||||
<br> |
|
||||
<img v-for="(dog,index) in dogList" :src="dog" :key="index" > |
|
||||
<button @click="getDog">再来一只狗</button> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script lang="ts" setup name="Person"> |
|
||||
import { ref,reactive } from 'vue'; |
|
||||
import axios from 'axios'; |
|
||||
|
|
||||
//数据 |
|
||||
let sum = ref(0) |
|
||||
let dogList = reactive([ |
|
||||
'https://images.dog.ceo/breeds/pembroke/n02113023_4269.jpg' |
|
||||
]) |
|
||||
|
|
||||
|
|
||||
//方法 |
|
||||
function add(){ |
|
||||
sum.value+=1 |
|
||||
} |
|
||||
|
|
||||
async function getDog(){ |
|
||||
try{ |
|
||||
let result = await axios.get('https://dog.ceo/api/breeds/image/random') |
|
||||
dogList.push(result.data.message) |
|
||||
}catch(error){ |
|
||||
alert(error) |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style scoped> |
|
||||
.person{ |
|
||||
width: 200px; |
|
||||
height: 200px; |
|
||||
background-color: palegoldenrod; |
|
||||
} |
|
||||
img{ |
|
||||
width: 100%; |
|
||||
height: auto; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,29 @@ |
|||||
|
import {defineStore} from 'pinia' |
||||
|
|
||||
|
export const useCountStore = defineStore('count',{ |
||||
|
actions:{ |
||||
|
// 定义方法,可以修改state中的数据,响应组件中的“动作”
|
||||
|
increment(value: number){ |
||||
|
console.log('increment被调用了',value) |
||||
|
//修改数据,响应组件
|
||||
|
//this是当前store实例
|
||||
|
this.sum += value |
||||
|
}, |
||||
|
//减
|
||||
|
decrement(value: number){ |
||||
|
this.sum -= value |
||||
|
} |
||||
|
}, |
||||
|
// 真正存储数据的地方
|
||||
|
state(){ |
||||
|
return { |
||||
|
sum:6 |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getters:{ |
||||
|
bigSum(state){ |
||||
|
return state.sum * 10 |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,22 @@ |
|||||
|
import {defineStore} from 'pinia' |
||||
|
import axios from 'axios' |
||||
|
import {nanoid} from 'nanoid' |
||||
|
|
||||
|
export const useTalkStore = defineStore('talk',{ |
||||
|
actions:{ |
||||
|
async getATalk(){ |
||||
|
// 发请求,下面这行的写法是:连续解构赋值+重命名
|
||||
|
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') |
||||
|
// 把请求回来的字符串,包装成一个对象
|
||||
|
let obj = {id:nanoid(),title} |
||||
|
// 放到数组中
|
||||
|
this.talkList.unshift(obj) |
||||
|
} |
||||
|
}, |
||||
|
// 真正存储数据的地方
|
||||
|
state(){ |
||||
|
return { |
||||
|
talkList:JSON.parse(localStorage.getItem('talkList') as string) || [] |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,387 @@ |
|||||
|
# SpringBoot+Redis |
||||
|
|
||||
|
前瞻: |
||||
|
|
||||
|
Reids的Java客户端有 |
||||
|
|
||||
|
1.Jedis |
||||
|
|
||||
|
2.Lettuce |
||||
|
|
||||
|
3.Spring Data Redis |
||||
|
|
||||
|
我们主要使用Spring Data Redis |
||||
|
|
||||
|
## 1.操作步骤: |
||||
|
|
||||
|
### 1.1 导入Spring Data Redis 的maven坐标 |
||||
|
|
||||
|
```xml |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-data-redis</artifactId> |
||||
|
</dependency> |
||||
|
``` |
||||
|
|
||||
|
### 1.2 配置redis数据源 |
||||
|
|
||||
|
``` |
||||
|
spring: |
||||
|
redis: |
||||
|
host: ${sky.redis.host} |
||||
|
port: ${sky.redis.port} |
||||
|
password: ${sky.redis.password} |
||||
|
database: ${sky.redis.database} |
||||
|
``` |
||||
|
|
||||
|
```yaml |
||||
|
sky: |
||||
|
redis: |
||||
|
host: localhost |
||||
|
port: 6379 |
||||
|
password: |
||||
|
database: 10 |
||||
|
``` |
||||
|
|
||||
|
### 1.3 编写配置类,创建RedisTemplete对象 |
||||
|
|
||||
|
```java |
||||
|
@Configuration |
||||
|
@Slf4j |
||||
|
public class RedisConfiguration { |
||||
|
@Bean |
||||
|
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){ |
||||
|
log.info("创建RedisTemplate对象..."); |
||||
|
RedisTemplate redisTemplate = new RedisTemplate(); |
||||
|
//设置redis连接工厂对象 |
||||
|
redisTemplate.setConnectionFactory(redisConnectionFactory); |
||||
|
//设置redis的key的序列化器 |
||||
|
redisTemplate.setKeySerializer(new StringRedisSerializer()); |
||||
|
return redisTemplate; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
 |
||||
|
|
||||
|
加redis的key的序列化器之前: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
加redis的key的序列化器之后: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
### 1.4 通过Redis Template对象操作Redis |
||||
|
|
||||
|
```java |
||||
|
@SpringBootTest |
||||
|
public class SpringDataRedisTest { |
||||
|
@Autowired |
||||
|
private RedisTemplate redisTemplate; |
||||
|
@Test |
||||
|
public void testRedisTemplate(){ |
||||
|
System.out.println(redisTemplate); |
||||
|
//操作字符串类型 |
||||
|
ValueOperations valueOperations = redisTemplate.opsForValue(); |
||||
|
HashOperations hashOperations = redisTemplate.opsForHash(); |
||||
|
ListOperations listOperations = redisTemplate.opsForList(); |
||||
|
SetOperations setOperations = redisTemplate.opsForSet(); |
||||
|
ZSetOperations zSetOperations = redisTemplate.opsForZSet(); |
||||
|
} |
||||
|
//String类型 |
||||
|
@Test |
||||
|
public void testString(){ |
||||
|
//set get setex setnx |
||||
|
redisTemplate.opsForValue().set("name","小明"); |
||||
|
String city = (String) redisTemplate.opsForValue().get("name"); |
||||
|
System.out.println(city); |
||||
|
redisTemplate.opsForValue().set("code","1234",3, TimeUnit.MINUTES); |
||||
|
//setIfAbsent就是setnx |
||||
|
redisTemplate.opsForValue().setIfAbsent("lock","1"); |
||||
|
redisTemplate.opsForValue().setIfAbsent("lock","2"); |
||||
|
} |
||||
|
//Hash类型 |
||||
|
@Test |
||||
|
public void testHash(){ |
||||
|
//hset hget hdel hkeys hvals |
||||
|
HashOperations hashOperations = redisTemplate.opsForHash(); |
||||
|
//hset |
||||
|
hashOperations.put("100","name","yinshunyu"); |
||||
|
hashOperations.put("100","age","20"); |
||||
|
//get |
||||
|
String name = (String) hashOperations.get("100", "name"); |
||||
|
System.out.println(name); |
||||
|
//hkeys |
||||
|
Set keys = hashOperations.keys("100"); |
||||
|
System.out.println(keys); |
||||
|
//hvals |
||||
|
List values = hashOperations.values("100"); |
||||
|
System.out.println(values); |
||||
|
//hdel |
||||
|
hashOperations.delete("100","age"); |
||||
|
} |
||||
|
//List类型 |
||||
|
@Test |
||||
|
public void testList(){ |
||||
|
//lpush lrange rpop llen |
||||
|
ListOperations listOperations = redisTemplate.opsForList(); |
||||
|
listOperations.leftPushAll("mylist","a","b","c"); |
||||
|
listOperations.leftPush("mylist","d"); |
||||
|
List mylist = listOperations.range("mylist", 0, -1); |
||||
|
System.out.println(mylist); |
||||
|
listOperations.rightPop("mylist"); |
||||
|
Long size = listOperations.size("mylist"); |
||||
|
System.out.println(size); |
||||
|
} |
||||
|
//Set类型 |
||||
|
@Test |
||||
|
public void testSet(){ |
||||
|
//sadd smembers scard sinter sunion srem |
||||
|
SetOperations setOperations = redisTemplate.opsForSet(); |
||||
|
setOperations.add("set1","a","b","c","d"); |
||||
|
setOperations.add("set2","a","b","x","y"); |
||||
|
//smembers 获取元素 |
||||
|
Set members = setOperations.members("set1"); |
||||
|
System.out.println(members); |
||||
|
//scard 元素个数 |
||||
|
Long size = setOperations.size("set1"); |
||||
|
System.out.println(size); |
||||
|
//sinter 交集 |
||||
|
Set intersect = setOperations.intersect("set1", "set2"); |
||||
|
System.out.println(intersect); |
||||
|
//sunion 并集 |
||||
|
Set union = setOperations.union("set1", "set2"); |
||||
|
System.out.println( union); |
||||
|
setOperations.remove("set1","a","b"); |
||||
|
} |
||||
|
//ZSet类型 |
||||
|
@Test |
||||
|
public void testZset(){ |
||||
|
//zadd zrange zincrby zrem |
||||
|
ZSetOperations zSetOperations = redisTemplate.opsForZSet(); |
||||
|
|
||||
|
zSetOperations.add("zset1","a",10); |
||||
|
zSetOperations.add("zset1","b",12); |
||||
|
zSetOperations.add("zset1","c",9); |
||||
|
|
||||
|
Set zset1 = zSetOperations.range("zset1", 0, -1); |
||||
|
System.out.println(zset1); |
||||
|
//zincrby 修改分数 |
||||
|
zSetOperations.incrementScore("zset1","c",10); |
||||
|
|
||||
|
zSetOperations.remove("zset1","a","b"); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## 2.缓存菜品模拟 |
||||
|
|
||||
|
**修改用户端接口 DishController 的 list 方法,加入缓存处理逻辑:** |
||||
|
|
||||
|
```java |
||||
|
@Autowired |
||||
|
private RedisTemplate redisTemplate; |
||||
|
/** |
||||
|
* 根据分类id查询菜品 |
||||
|
* |
||||
|
* @param categoryId |
||||
|
* @return |
||||
|
*/ |
||||
|
@GetMapping("/list") |
||||
|
@ApiOperation("根据分类id查询菜品") |
||||
|
public Result<List<DishVO>> list(Long categoryId) { |
||||
|
|
||||
|
//构造redis中的key,规则:dish_分类id |
||||
|
String key = "dish_" + categoryId; |
||||
|
|
||||
|
//查询redis中是否存在菜品数据 |
||||
|
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key); |
||||
|
if(list != null && list.size() > 0){ |
||||
|
//如果存在,直接返回,无须查询数据库 |
||||
|
return Result.success(list); |
||||
|
} |
||||
|
//////////////////////////////////////////////////////// |
||||
|
Dish dish = new Dish(); |
||||
|
dish.setCategoryId(categoryId); |
||||
|
dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品 |
||||
|
|
||||
|
//如果不存在,查询数据库,将查询到的数据放入redis中 |
||||
|
list = dishService.listWithFlavor(dish); |
||||
|
//////////////////////////////////////////////////////// |
||||
|
redisTemplate.opsForValue().set(key, list); |
||||
|
|
||||
|
return Result.success(list); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**抽取清理缓存的方法:** |
||||
|
|
||||
|
在管理端DishController中添加 |
||||
|
|
||||
|
```java |
||||
|
@Autowired |
||||
|
private RedisTemplate redisTemplate; |
||||
|
/** |
||||
|
* 清理缓存数据 |
||||
|
* @param pattern |
||||
|
*/ |
||||
|
private void cleanCache(String pattern){ |
||||
|
Set keys = redisTemplate.keys(pattern); |
||||
|
redisTemplate.delete(keys); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**调用清理缓存的方法,保证数据一致性:** |
||||
|
|
||||
|
**1). 新增菜品优化** |
||||
|
|
||||
|
```java |
||||
|
/** |
||||
|
* 新增菜品 |
||||
|
* |
||||
|
* @param dishDTO |
||||
|
* @return |
||||
|
*/ |
||||
|
@PostMapping |
||||
|
@ApiOperation("新增菜品") |
||||
|
public Result save(@RequestBody DishDTO dishDTO) { |
||||
|
log.info("新增菜品:{}", dishDTO); |
||||
|
dishService.saveWithFlavor(dishDTO); |
||||
|
|
||||
|
//清理缓存数据 |
||||
|
String key = "dish_" + dishDTO.getCategoryId(); |
||||
|
cleanCache(key); |
||||
|
return Result.success(); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
# EasyExcel初步理解 |
||||
|
|
||||
|
## 依赖导入 |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --> |
||||
|
<dependency> |
||||
|
<groupId>com.alibaba</groupId> |
||||
|
<artifactId>easyexcel</artifactId> |
||||
|
<version>2.1.1</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.projectlombok</groupId> |
||||
|
<artifactId>lombok</artifactId> |
||||
|
<version>1.18.10</version> |
||||
|
``` |
||||
|
|
||||
|
### 创建与表格对应的实体类 |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
@NoArgsConstructor |
||||
|
public class Student { |
||||
|
|
||||
|
@ExcelProperty(value = "学生id") |
||||
|
private Integer id; |
||||
|
@ExcelProperty(value = "学生姓名") |
||||
|
private String name; |
||||
|
|
||||
|
@ExcelProperty(value = "学生年龄") |
||||
|
private Integer age; |
||||
|
} |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
## 写操作 |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
public class WriteExcel { |
||||
|
public static void main(String[] args) { |
||||
|
|
||||
|
//准备文件路径 |
||||
|
String fileName="D:/destory/test/easyexcel.xls"; |
||||
|
//写出文件 |
||||
|
EasyExcel.write(fileName, Student.class).sheet("easyexcel") |
||||
|
.doWrite(data()); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private static List<Student> data(){ |
||||
|
ArrayList<Student> list = new ArrayList<>(); |
||||
|
for (int i = 0; i < 10; i++) { |
||||
|
Student student = new Student(i, "董德" + 1, 22 + i); |
||||
|
list.add(student); |
||||
|
} |
||||
|
return list; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 读操作 |
||||
|
|
||||
|
### 改造实体类 |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
@NoArgsConstructor |
||||
|
public class Student { |
||||
|
|
||||
|
@ExcelProperty(value = "学生id",index = 0) |
||||
|
private Integer id; |
||||
|
@ExcelProperty(value = "学生姓名",index = 1) |
||||
|
private String name; |
||||
|
|
||||
|
@ExcelProperty(value = "学生年龄",index = 2) |
||||
|
private Integer age; |
||||
|
} |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
### 创建监听器 |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
public class EasyExcelLinster extends AnalysisEventListener<Student> { |
||||
|
|
||||
|
List<Student> list= new ArrayList<Student>(); |
||||
|
//一行一行的去读取里面的数据 |
||||
|
@Override |
||||
|
public void invoke(Student student, AnalysisContext analysisContext) { |
||||
|
System.out.println(student); |
||||
|
list.add(student); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void doAfterAllAnalysed(AnalysisContext analysisContext) { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
### 读取 |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
public class ReadExcel { |
||||
|
public static void main(String[] args) { |
||||
|
//准备文件路径 |
||||
|
String fileName="D:/destory/test/easyexcel.xls"; |
||||
|
EasyExcel.read(fileName, Student.class, new ExcelListener()).sheet().doRead(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
``` |
||||
|
|
||||
@ -0,0 +1,434 @@ |
|||||
|
<template> |
||||
|
<div class="exam-system"> |
||||
|
<!-- 头部标题 --> |
||||
|
<header class="header"> |
||||
|
<div class="logo"> |
||||
|
<span class="icon">📋</span> |
||||
|
<span class="title">股票知识评测系统</span> |
||||
|
</div> |
||||
|
<div class="subtitle">全方面评估您股票知识水平,还能个性化学习建议</div> |
||||
|
</header> |
||||
|
|
||||
|
<div class="main-content"> |
||||
|
<!-- 左侧题目区域 --> |
||||
|
<div class="left-panel"> |
||||
|
<!-- 题目显示区 --> |
||||
|
<div class="question-area"> |
||||
|
<div class="question-header"> |
||||
|
<input type="text" v-model="searchText" placeholder="搜索题目..." class="search-input" /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="question-content"> |
||||
|
<h3 class="question-title">{{ currentQuestion.title }}</h3> |
||||
|
|
||||
|
<div class="options"> |
||||
|
<div |
||||
|
v-for="option in currentQuestion.options" |
||||
|
:key="option.key" |
||||
|
class="option-item" |
||||
|
:class="{ 'selected': selectedAnswer === option.key }" |
||||
|
@click="selectAnswer(option.key)" |
||||
|
> |
||||
|
<span class="radio-btn" :class="{ 'checked': selectedAnswer === option.key }"></span> |
||||
|
<span class="option-label">{{ option.key }}. {{ option.text }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 答题卡预览区 --> |
||||
|
<div class="answer-preview"> |
||||
|
<div class="preview-content"> |
||||
|
<!-- 可以显示答题进度或其他信息 --> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 底部按钮 --> |
||||
|
<div class="bottom-actions"> |
||||
|
<button class="btn btn-secondary" @click="submitExam">提交</button> |
||||
|
<button class="btn btn-primary" @click="previousQuestion">← 上一题</button> |
||||
|
<button class="btn btn-primary" @click="nextQuestion">下一题 →</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 右侧答题卡 --> |
||||
|
<div class="right-panel"> |
||||
|
<div class="answer-card"> |
||||
|
<div class="card-header"> |
||||
|
<span class="folder-icon">📁</span> |
||||
|
<span class="card-title">试题导航</span> |
||||
|
</div> |
||||
|
|
||||
|
<div class="card-grid"> |
||||
|
<div |
||||
|
v-for="(item, index) in totalQuestions" |
||||
|
:key="index" |
||||
|
class="grid-item" |
||||
|
:class="getQuestionStatus(index)" |
||||
|
@click="jumpToQuestion(index)" |
||||
|
> |
||||
|
{{ index + 1 }} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { ref, computed } from 'vue' |
||||
|
|
||||
|
// 搜索文本 |
||||
|
const searchText = ref('') |
||||
|
|
||||
|
// 当前题目索引 |
||||
|
const currentQuestionIndex = ref(0) |
||||
|
|
||||
|
// 用户答案记录 |
||||
|
const userAnswers = ref({}) |
||||
|
|
||||
|
// 选中的答案 |
||||
|
const selectedAnswer = ref('') |
||||
|
|
||||
|
// 总题目数 |
||||
|
const totalQuestions = ref(25) |
||||
|
|
||||
|
// 题目数据(示例) |
||||
|
const questions = ref([ |
||||
|
{ |
||||
|
id: 1, |
||||
|
title: '以下哪个不属于股票的基本特征?', |
||||
|
options: [ |
||||
|
{ key: 'A', text: '收益性' }, |
||||
|
{ key: 'B', text: '风险性' }, |
||||
|
{ key: 'C', text: '流动性' }, |
||||
|
{ key: 'D', text: '确定性' } |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
id: 2, |
||||
|
title: '股票市场的主要功能是什么?', |
||||
|
options: [ |
||||
|
{ key: 'A', text: '资金融通' }, |
||||
|
{ key: 'B', text: '价格发现' }, |
||||
|
{ key: 'C', text: '资源配置' }, |
||||
|
{ key: 'D', text: '以上都是' } |
||||
|
] |
||||
|
} |
||||
|
// 可以添加更多题目... |
||||
|
]) |
||||
|
|
||||
|
// 当前题目 |
||||
|
const currentQuestion = computed(() => { |
||||
|
return questions.value[currentQuestionIndex.value] || questions.value[0] |
||||
|
}) |
||||
|
|
||||
|
// 选择答案 |
||||
|
const selectAnswer = (answer) => { |
||||
|
selectedAnswer.value = answer |
||||
|
userAnswers.value[currentQuestionIndex.value] = answer |
||||
|
} |
||||
|
|
||||
|
// 上一题 |
||||
|
const previousQuestion = () => { |
||||
|
if (currentQuestionIndex.value > 0) { |
||||
|
currentQuestionIndex.value-- |
||||
|
selectedAnswer.value = userAnswers.value[currentQuestionIndex.value] || '' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 下一题 |
||||
|
const nextQuestion = () => { |
||||
|
if (currentQuestionIndex.value < questions.value.length - 1) { |
||||
|
currentQuestionIndex.value++ |
||||
|
selectedAnswer.value = userAnswers.value[currentQuestionIndex.value] || '' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 跳转到指定题目 |
||||
|
const jumpToQuestion = (index) => { |
||||
|
currentQuestionIndex.value = index |
||||
|
selectedAnswer.value = userAnswers.value[index] || '' |
||||
|
} |
||||
|
|
||||
|
// 获取题目状态 |
||||
|
const getQuestionStatus = (index) => { |
||||
|
if (index === currentQuestionIndex.value) { |
||||
|
return 'current' |
||||
|
} |
||||
|
if (userAnswers.value[index]) { |
||||
|
return 'answered' |
||||
|
} |
||||
|
return '' |
||||
|
} |
||||
|
|
||||
|
// 提交考试 |
||||
|
const submitExam = () => { |
||||
|
const answeredCount = Object.keys(userAnswers.value).length |
||||
|
if (answeredCount < questions.value.length) { |
||||
|
if (confirm(`您还有 ${questions.value.length - answeredCount} 道题未作答,确定要提交吗?`)) { |
||||
|
// 提交逻辑 |
||||
|
console.log('提交答案:', userAnswers.value) |
||||
|
} |
||||
|
} else { |
||||
|
// 提交逻辑 |
||||
|
console.log('提交答案:', userAnswers.value) |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.exam-system { |
||||
|
min-height: 100vh; |
||||
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); |
||||
|
color: #fff; |
||||
|
font-family: 'Microsoft YaHei', Arial, sans-serif; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
padding: 20px 40px; |
||||
|
background: rgba(0, 0, 0, 0.2); |
||||
|
} |
||||
|
|
||||
|
.logo { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 10px; |
||||
|
font-size: 24px; |
||||
|
font-weight: bold; |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.icon { |
||||
|
font-size: 28px; |
||||
|
} |
||||
|
|
||||
|
.subtitle { |
||||
|
color: rgba(255, 255, 255, 0.8); |
||||
|
font-size: 14px; |
||||
|
margin-left: 38px; |
||||
|
} |
||||
|
|
||||
|
.main-content { |
||||
|
display: flex; |
||||
|
gap: 20px; |
||||
|
padding: 20px 40px; |
||||
|
max-width: 1400px; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
|
||||
|
.left-panel { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 20px; |
||||
|
} |
||||
|
|
||||
|
.question-area { |
||||
|
background: rgba(20, 40, 80, 0.6); |
||||
|
border: 1px solid rgba(100, 150, 200, 0.3); |
||||
|
border-radius: 8px; |
||||
|
padding: 20px; |
||||
|
} |
||||
|
|
||||
|
.question-header { |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
.search-input { |
||||
|
width: 100%; |
||||
|
padding: 10px 15px; |
||||
|
background: rgba(0, 0, 0, 0.3); |
||||
|
border: 1px solid rgba(100, 150, 200, 0.3); |
||||
|
border-radius: 4px; |
||||
|
color: #fff; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.search-input::placeholder { |
||||
|
color: rgba(255, 255, 255, 0.4); |
||||
|
} |
||||
|
|
||||
|
.question-content { |
||||
|
padding: 20px 0; |
||||
|
} |
||||
|
|
||||
|
.question-title { |
||||
|
color: rgba(255, 255, 255, 0.6); |
||||
|
font-size: 14px; |
||||
|
margin-bottom: 20px; |
||||
|
font-weight: normal; |
||||
|
} |
||||
|
|
||||
|
.options { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 15px; |
||||
|
} |
||||
|
|
||||
|
.option-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 12px; |
||||
|
padding: 12px 15px; |
||||
|
background: rgba(0, 0, 0, 0.2); |
||||
|
border: 1px solid rgba(100, 150, 200, 0.3); |
||||
|
border-radius: 4px; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.option-item:hover { |
||||
|
background: rgba(100, 150, 200, 0.2); |
||||
|
border-color: rgba(100, 150, 200, 0.5); |
||||
|
} |
||||
|
|
||||
|
.option-item.selected { |
||||
|
background: rgba(220, 80, 80, 0.3); |
||||
|
border-color: rgba(220, 80, 80, 0.6); |
||||
|
} |
||||
|
|
||||
|
.radio-btn { |
||||
|
width: 18px; |
||||
|
height: 18px; |
||||
|
border: 2px solid rgba(255, 255, 255, 0.5); |
||||
|
border-radius: 50%; |
||||
|
display: inline-block; |
||||
|
position: relative; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.radio-btn.checked { |
||||
|
border-color: #e74c3c; |
||||
|
background: #e74c3c; |
||||
|
} |
||||
|
|
||||
|
.radio-btn.checked::after { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 8px; |
||||
|
height: 8px; |
||||
|
background: #fff; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
.option-label { |
||||
|
font-size: 15px; |
||||
|
} |
||||
|
|
||||
|
.answer-preview { |
||||
|
background: rgba(20, 40, 80, 0.6); |
||||
|
border: 1px solid rgba(100, 150, 200, 0.3); |
||||
|
border-radius: 8px; |
||||
|
padding: 20px; |
||||
|
min-height: 150px; |
||||
|
} |
||||
|
|
||||
|
.bottom-actions { |
||||
|
display: flex; |
||||
|
gap: 15px; |
||||
|
justify-content: flex-start; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
padding: 12px 30px; |
||||
|
border: none; |
||||
|
border-radius: 4px; |
||||
|
font-size: 14px; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.btn-primary { |
||||
|
background: rgba(60, 120, 180, 0.8); |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.btn-primary:hover { |
||||
|
background: rgba(60, 120, 180, 1); |
||||
|
} |
||||
|
|
||||
|
.btn-secondary { |
||||
|
background: rgba(60, 120, 180, 0.8); |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.btn-secondary:hover { |
||||
|
background: rgba(70, 130, 190, 1); |
||||
|
} |
||||
|
|
||||
|
.right-panel { |
||||
|
width: 280px; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.answer-card { |
||||
|
background: rgba(20, 40, 80, 0.6); |
||||
|
border: 1px solid rgba(100, 150, 200, 0.3); |
||||
|
border-radius: 8px; |
||||
|
padding: 20px; |
||||
|
} |
||||
|
|
||||
|
.card-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
margin-bottom: 20px; |
||||
|
padding-bottom: 15px; |
||||
|
border-bottom: 1px solid rgba(100, 150, 200, 0.2); |
||||
|
} |
||||
|
|
||||
|
.folder-icon { |
||||
|
font-size: 20px; |
||||
|
} |
||||
|
|
||||
|
.card-title { |
||||
|
color: #6db3f2; |
||||
|
font-size: 16px; |
||||
|
} |
||||
|
|
||||
|
.card-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(5, 1fr); |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
.grid-item { |
||||
|
aspect-ratio: 1; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
background: rgba(60, 120, 180, 0.6); |
||||
|
border-radius: 4px; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.grid-item:hover { |
||||
|
background: rgba(60, 120, 180, 0.8); |
||||
|
transform: scale(1.05); |
||||
|
} |
||||
|
|
||||
|
.grid-item.current { |
||||
|
background: #3498db; |
||||
|
box-shadow: 0 0 10px rgba(52, 152, 219, 0.5); |
||||
|
} |
||||
|
|
||||
|
.grid-item.answered { |
||||
|
background: rgba(60, 120, 180, 0.8); |
||||
|
} |
||||
|
|
||||
|
.grid-item:nth-child(1) { |
||||
|
background: #e74c3c; |
||||
|
} |
||||
|
|
||||
|
.grid-item:nth-child(2) { |
||||
|
background: #27ae60; |
||||
|
} |
||||
|
</style> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue