当前位置:   article > 正文

我花了5天时间,开发了一个在线学习的小网站【集合】_咪猫学习网

咪猫学习网

大三寒假赋闲在家,闲来无事,用了5天时间做了一个在线学习的小网站,一鼓作气部署上线,制作的过程比较坎坷。内心经历过奔溃,也经历过狂喜。

按照惯例先放出网址,欢迎大家来访问学习:www.pbjlovezjy.com

咪猫学习网 (pbjlovezjy.com)​​​​​​

我采用的技术栈是:SpringBoot + MySQL + Mybatis + Vue.js + Redis

虽然最开始有想过用微服务来做,但后来考虑到这只是一个小网站,秉持着杀鸡焉用牛刀的原则,就直接采用前后端分离的架构来做了。

做这个网站的浅层目的:主要是提供一些能够在线学习的资料,比如之前在复习新思想、毛中特这些思政课的时候,会发现找不到课后题的答案,最后花费了很多时间,东拼西凑,才最终找齐,感觉很浪费时间。

同时资料也很难做到随时随地背诵,有时候上厕所想背一下,都要把电脑上的PDF传到QQ里,才能打开看看,而且字体很小,看着很累人。

所以我就做了一个能够在线学习的网站,同学们不论是走路、坐车、吃饭还是睡觉,只需要打开网页,然后想看哪章点哪章就能在线复习了,简直就是懒人福音,非常nice。

做这个网站的深层目的:是为了能够给更多的大学生提供便捷的资源获取渠道,网络上的那些资料质量参差不齐,像我以前年少、涉世未深的时候也经常被骗,所以不希望有同学被一些不良商家割了韭菜,无情坑骗,买到不适合的资料。

因此我只收取少额的费用,比如购买1门课1.2元,5门课4.88元,平均1门课不到1元钱,在1年时间内可以无限次在线看,为的是能够让更广大的学生同胞们能够负担的起。

同时勉强维持我运营维护的费用,比如租一台服务器要大几百块,购买资料也要小几百块,平均一年的支出是千把块,当前仍处于入不敷出的状态o(╥﹏╥)o,也希望好心朋友们能捐赠一下。

我计划将捐赠和付费的金额,用于购买更多资料的使用权,在征得同意的前提下,开源分享给那些有需要的学子们。

下面厚颜无耻贴个赞赏码,如果愿意给俺捐款,请务必备注清名称,让我知道您是哪位好心人。

下面言归正传:

我主要想说开发过程中3件令我感觉比较崩溃的事情:

第1件事,让我足足奔溃了1个晚上——数据录入。没错就是数据录入,整个网站虽然只有2张表,但content这张表却让我足足录入了6个小时...

起初我是想把数据直接写在html中,但这样写死之后后期不方便维护,所以我就想:通过搭建一个html模板来实时获取数据库中的数据,对数据进行展示。这样后期需要改动的时候,我只需要打开数据库进行修改就好。html框架如下:

  1. <template>
  2. <div class="app sidebar-mini" id="app">
  3. <div :style="{ marginLeft: this.$store.getters.status ? '70px' : '240px', width:'84%' , marginTop: '60px' }">
  4. <div class="tile mb-4" >
  5. <h2 id="typography">第一章:新时代坚持和发展中国特色社会主义</h2>
  6. </div>
  7. <div v-for="(section, index) in sections" :key="index">
  8. <div class="tile mb-4">
  9. <h4>{{section.title}}</h4>
  10. <div v-for="(part,index) in section.content" :key="index">
  11. <p style="text-align: left;font-size: 18px;line-height: 1.9;text-indent: 2em;">{{part}}</p>
  12. </div>
  13. </div>
  14. </div>
  15. </div>
  16. </div>
  17. </template>

但是数据录入数据库的过程真的很枯燥繁琐,我也没想到有什么好方法能一次性把数据录入。

就拿新思想来举例,一门新思想课程分为17个大章节,在每个大章节里面还有4-5个问题,这样一门课下来要录入6、7十条的数据,一方面录入的数量很多,另一方面还要考虑分段和展示的问题,所以要求录入很精细,速度很慢,加上做的是重复动作,因此感觉很疲劳崩溃。

甚至有那么一瞬间,我似乎明白了程序员为啥会戏称自己为码农了。。

第2件事,让我整整难受了1天。那当属用拦截器来做权限的管理

之所以在我的这个网站中需要做权限的管理,主要是为了区分出3类用户:1.未登录用户。2.已登录用户,但未付费。3.已付费用户。

不同类型的用户只能访问资源的不同章节,比如未登录用户只能访问前10%的章节,已登录但未付费用户,只能访问前30%的章节,只有付费用户才能访问全部章节。

之所以不敢让未登录用户访问太多章节的原因是,怕出现安全问题。比如不排除某些“坏人”,会通过疯狂访问公共资源来破坏数据库,使其过负载。

有人可能会说你可以把公共资源放到redis缓存里呀,这样你就不用害怕“坏人”DOS攻击了(但很显然我想到了,现在还没来得及这么做,不过后面应该会做)。  

因此我选择仅开放一部分的资源,另一部分资源用拦截器拦截下来,避免对数据库造成冲击。

下面给大家简单看一下拦截器:

实话说代码并不复杂, 但逻辑比较复杂。比如JwtTokenAdminInterceptor这个拦截器主要是拦截未登录的用户,放行公共资源。然后TestInterceptor这个拦截器主要是为了拦截未付费用户,放行已登录用户能访问的所有资源。

然而这里面还有更复杂的,比如用户购买2门课程,可以是毛中特和新思想,也可以是近纲和马原。

一开始我觉得这是一个排列组合的问题,一个人在5门课中,可以选择只买1门,也可以选择全买,也可以选择买2门或者3门或者4门。这样子的话组件的情况会变得非常多,差不多是22种情况,难道每种情况都要写一个拦截器吗?

但经过我的苦思冥想(实则是GPT给我的思路),我采用了一个string来模拟bitmap,当某1位上为5代表用户没购买,当某1位上为1代表用户购买,只需要把为5的资源对应的请求地址加入到拦截器中即可,这样就可以实现只拦截用户没购买的资源。

之所以我最开始没想到这点,还是因为对拦截器的性质不太熟练

具体实现可以详见上面的代码。

第3件事,让我整整难受了3天。那当属网站上线后改前端的界面和BUG

因为在电脑上看页面屏宽很宽,可以显示完全,如下图:

但因为手机屏幕的宽度只有450作用,所以PC端看上去好好的界面,一到手机上就会变成一坨*。

此时有2种解决方案:

1.单独设置css样式PC端和移动端的代码,使得用一套代码来实现2种页面效果。

2.写2套代码,2套界面,通过地址访问不同的界面。这种一般是早期的做法,工作量太大。

秉持敏捷开发的原则,我舍弃了第2种方法,采用了第1种方法。

然后涉及到几个问题:

比如学习制作自适应页面代码的语法,然后一步步调试。

还有在一些不熟悉的css组间中反复挣扎,一会儿组间要横向摆放,比如“资源付费”界面,一会儿要竖向摆放;一会儿这个组件消失了,一会儿那个组件被遮挡了,要调位置还要调优先级。

一会儿这里好了,一会儿那里出问题了,在崩溃和喜悦之间反复横跳。

这里的过程就不多说了,实在是辛酸的血泪史。

中间还有一些插曲,比如说我的导航栏和侧栏是单独放在App.vue里的,然后主体界面是在vuews里面,就会涉及到不同组件之间共享变量的调用问题。

所以我感觉最难的根本不是后端代码,而是前端代码。。。

好了好了,接下来就说一些值得开心和喜悦的事吧。

第1件事,当然是独立部署上线了一个网站,能够让亲朋好友们在线访问,跟他们说这是我的网站,真心有满满的自豪感啊。

第2件事,攻克了很多的困难,从0-1搭建了一个网站,包括后端和前端,把一些学习到的知识现学现用,活学活用,加深了对知识的理解,在过程中也很享受改BUG的过程,当一切都基本完成之后,心中的喜悦明显会居多。

下面是一些代办事项,看着一件件待办事项被完成、划掉,内心充满自豪感。

但是不可否认的是网站当前还存在有一些问题,比如但不仅限于如下:

1.没有把公共资源缓存到Redis中;

2.SSL证书还没有办理,导致网站属于不安全状态;

3.数据库的设计不太合理,不符合第3范式。

4.界面上仍旧有需要改进的地方,比如在移动端显示下登录栏和注册栏时常会“分家”,一个掉到下面,一个挂在上面;

5.还有登录和注册的安全机制仍旧有待加强,比如还没有限制1人1手机只能注册1个账号;登录的时候也没有验证码,无法防止机器人恶意登录;登录校验也没有用更安全的Spring Security,仅仅只用了一个JWT的Token校验。

综上:我想说的是,尽管世界就像代码一样,仍旧有许多不完美的地方,但我相信在新的一年,会通过你我的共同努力,让其慢慢变得更好。

在此祝新老粉丝朋友们,新的一年心想事成,万事如意,龙年大吉。对那些愿意给西风捐赠的老板们,道一声诚挚的感谢。

P499 集合介绍

数组不足之处:1.数组长度必须指定,一旦指定,不能修改。2.数组内的元素必须是同一类型。3.使用数组增加/删除元素的示意代码,比较麻烦。

集合的优点:1.可以动态保存任意多个对象,使用比较方便。2.提供了一系列方便的操作对象的方法:add、remove、set、get等。3.使用集合添加、删除新元素的示意代码。

P500 集合体系图

集合主要分为2组:

1.单列集合(在集合里放单个元素)

Collection里面有2个重要的子接口list和set。

2.双列集合(在集合里放键值对元素)

P501 Collection方法

public interface Collection<E> extends iterable<E>

1.collection实现子类可以存放多个元素,每个元素可以是Object

2.有些Collection的实现类,可以存放重复元素。

3.List是有序的,Set是无序的。

4.Collection接口没有直接实现类,是通过子接口Set和List来实现的。

P502 迭代器遍历

1.Iterator对象成为迭代器,用于遍历Collection集合中的元素。

2.所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即返回一个迭代器。

3.Iterator用于遍历集合

iterator.hasNext()可以判断是否有下一个元素,iterator.next()可以获取下一个元素。

  1. public class Main {
  2. public static void main(String[] args) {
  3. Collection col = new ArrayList();
  4. col.add(new Book("三国演义","罗贯中",10.1));
  5. col.add(new Book("小李飞刀","古龙",5.1));
  6. col.add(new Book("红楼梦","曹雪芹",34.6));
  7. Iterator iterator = col.iterator();
  8. while(iterator.hasNext()){//判断迭代器是否为空
  9. Object obj = iterator.next();
  10. System.out.println(obj);
  11. }
  12. iterator = col.iterator(); //重置迭代器
  13. while(iterator.hasNext()){
  14. Object obj = iterator.next();
  15. System.out.println(obj);
  16. }
  17. }
  18. }
  19. class Book{
  20. private String name;
  21. private String author;
  22. private double price;
  23. public Book(String name, String author, double price) {
  24. this.name = name;
  25. this.author = author;
  26. this.price = price;
  27. }
  28. public String getName() {return name;}
  29. public void setName(String name) {this.name = name;}
  30. public String getAuthor() {return author;}
  31. public void setAuthor(String author) {this.author = author;}
  32. public double getPrice() {return price;}
  33. public void setPrice(double price) {this.price = price;}
  34. @Override
  35. public String toString() {
  36. return "Book{" +
  37. "name='" + name + '\'' +
  38. ", author='" + author + '\'' +
  39. ", price=" + price +
  40. '}';
  41. }
  42. }

P503 集合增强for

语法如下:

  1. for(元素类型 元素名:集合名或数组名){
  2. 访问元素
  3. }

 增强for的底层仍然是迭代器,能够遍历集合和数组。

P505 List接口方法

List接口基本介绍:

1.往list集合中添加的元素是有序的,可以理解为队列,先进先出。

2.list集合里面的元素可以重复。

方法:

list.indexOf(对象名称)   可以返回该对象所在位置

list.add(下标,对象名称)   可以在下标的位置插入对象,原本的对象后移

list.set(下标,对象名称)  可以将下标位置的元素改为新的对象

list.subList(开始位置下标,结束位置下标)   可以返回开始到结束的对象,前闭后开,取不到结束位置

P508 List排序练习

  1. public class Main {
  2. public static void main(String[] args) {
  3. List list = new ArrayList();
  4. list.add(new Books("红楼梦","曹雪芹",100));
  5. list.add(new Books("西游记","吴承恩",10));
  6. list.add(new Books("水浒传","施耐庵",9));
  7. list.add(new Books("三国演义","罗贯中",80));
  8. sort(list);
  9. for (Object o :list) {
  10. System.out.println(o);
  11. }
  12. }
  13. public static void sort(List list){
  14. for(int i=0;i<list.size()-1;i++)
  15. for(int j=0;j<list.size()-1-i;j++){
  16. //取出对象Book
  17. Books books1 = (Books) list.get(j);
  18. Books books2 = (Books) list.get(j + 1);
  19. if(books1.getPrice()> books2.getPrice()){
  20. list.set(j,books2);
  21. list.set(j+1,books1);
  22. }
  23. }
  24. }
  25. }
  26. class Books{
  27. private String name;
  28. private String author;
  29. private double price;
  30. public Books(String name, String author, double price) {
  31. this.name = name;
  32. this.author = author;
  33. this.price = price;
  34. }
  35. public String getName() {return name;}
  36. public void setName(String name) {this.name = name;}
  37. public String getAuthor() {return author;}
  38. public void setAuthor(String author) {this.author = author;}
  39. public double getPrice() {return price;}
  40. public void setPrice(double price) {this.price = price;}
  41. @Override
  42. public String toString() {return "名称:"+name+"\t\t价格:"+price +"\t\t作者:"+author;}
  43. }

P509 ArrayList注意事项

ArrayList里面可以放入空值。

ArrayList是线程不安全的,因为没有synchronized关键字修饰。

在多线程下可以使用vector。

P510 ArrayList扩容机制

1. ArrayList中维护了一个Object类型的数组elementData。transient Object[] elementData。

transient表示瞬间、短暂的,表示该属性不会被序列化。

2. 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如果要再次扩容,则扩容elementData为1.5倍。(比如从0到10到15到22到33...)

3.如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。(比如从8到12到18到27)

P511 ArrayList底层源码1

分析使用无参构造器,创建和使用ArrayList的源码:

1.调用无参构造方法,创建了一个空的elementData数组。

2.执行add方法。先确定是否要扩容。确定数组大小可容纳之后再进行赋值。

3.该方法确定minCapacity。第1次扩容为10。

4.modCount是记录当前集合被修改的次数(防止多个线程同时修改)。minCapacity-elementData.length>0的意思是如果最小容量大于当前数组实际大小,数组长度不够,调用grow进行扩容。

5.oldCapacity+oldCapacity>>1相当于是oldCapacity的1.5倍。

第1次oldCapacity和newCapacity为0,会进入第1个if语句,所以minCapacity=10的值会被赋给newCapacity。

第2次及以后就是按照1.5倍进行扩容。

扩容使用的是Arrays.copyof(),因为要把原数组的数据拷贝到新数组。

注意:IDEA在默认情况下,Debug显示的数据是简化后的,如果希望看到完整的数据,需要设置。

P512 ArrayList底层源码2

下图是调用有参构造器,对数组初始化,进行扩容的情况:实则是创建了一个指定大小的elementData数组。

如果是有参数的构造器,扩容机制:

1.第一次扩容就按照elementData的1.5倍扩容。

2.整个执行的流程还是和前面一样。

P513 Vector注意事项

Vector带有synchronized是线程安全的。

单线程操作的情况用ArrayList,多线程操作的情况用Vector。

P514 Vector源码解读

Vector无参时大小默认为10,扩容是按2倍进行扩容。

首先进行装箱:

‘’

下面这个方法添加数据到集合:

确定是否需要扩容,elementData.length为当前数组大小,minCapacity为当前存入元素的个数。

如果需要的数组大小不够用就扩容。第1次扩容时capacityIncrement为0,因此走的是oldCapacity这条,而oldCapacity一开始为10,因此newCapacity为20。

P515 双向链表模拟

特点:

1.LinkedList底层实现了双向链表和双端队列的特点。

2.可以添加任意元素(元素可以重复),包括null。

3.线程不安全,没有实现同步。

LinkedList的底层结构:

1.LinkedList底层维护了一个双向链表。

2.LinkedList中维护了两个属性first和last分别指向首节点和尾节点。

3.每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表。

4.所以LinkedList元素的添加和删除,不是通过数组完成的,相对来说效率较高。

P516 LinKedList源码图解

装箱操作如下:

一开始last为null,l也等于null。new Node<>(l,e,null)表示prev指向null,next也指向null。

对于新节点:newNode和last和first都指向newNode。最初新节点的示意图如下:

对于第2个新节点,new Node<>(l,e,null)其中l代表新节点的前屈节点,指向的是前面一个节点,然后last会指向newNode,最后旧节点l的后继节点会指向新节点,很妙,如下图:

linkedList.remove()方法用于删除节点,没传参数默认删除第1个节点。

将f指向的双向链表的第一个节点拿掉

调用linkedList.set(索引下标,新值) 把对象为索引下标中的值改为新值。

调用linkedLide.get(下标) 可以得到下标中的对象。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/379591
推荐阅读
相关标签
  

闽ICP备14008679号