赞
踩
大三寒假赋闲在家,闲来无事,用了5天时间做了一个在线学习的小网站,一鼓作气部署上线,制作的过程比较坎坷。内心经历过奔溃,也经历过狂喜。
按照惯例先放出网址,欢迎大家来访问学习:www.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框架如下:
- <template>
- <div class="app sidebar-mini" id="app">
- <div :style="{ marginLeft: this.$store.getters.status ? '70px' : '240px', width:'84%' , marginTop: '60px' }">
- <div class="tile mb-4" >
- <h2 id="typography">第一章:新时代坚持和发展中国特色社会主义</h2>
- </div>
- <div v-for="(section, index) in sections" :key="index">
- <div class="tile mb-4">
- <h4>{{section.title}}</h4>
- <div v-for="(part,index) in section.content" :key="index">
- <p style="text-align: left;font-size: 18px;line-height: 1.9;text-indent: 2em;">{{part}}</p>
- </div>
- </div>
- </div>
- </div>
- </div>
- </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校验。
综上:我想说的是,尽管世界就像代码一样,仍旧有许多不完美的地方,但我相信在新的一年,会通过你我的共同努力,让其慢慢变得更好。
在此祝新老粉丝朋友们,新的一年心想事成,万事如意,龙年大吉。对那些愿意给西风捐赠的老板们,道一声诚挚的感谢。
数组不足之处:1.数组长度必须指定,一旦指定,不能修改。2.数组内的元素必须是同一类型。3.使用数组增加/删除元素的示意代码,比较麻烦。
集合的优点:1.可以动态保存任意多个对象,使用比较方便。2.提供了一系列方便的操作对象的方法:add、remove、set、get等。3.使用集合添加、删除新元素的示意代码。
集合主要分为2组:
1.单列集合(在集合里放单个元素)
Collection里面有2个重要的子接口list和set。
2.双列集合(在集合里放键值对元素)
public interface Collection<E> extends iterable<E>
1.collection实现子类可以存放多个元素,每个元素可以是Object
2.有些Collection的实现类,可以存放重复元素。
3.List是有序的,Set是无序的。
4.Collection接口没有直接实现类,是通过子接口Set和List来实现的。
1.Iterator对象成为迭代器,用于遍历Collection集合中的元素。
2.所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即返回一个迭代器。
3.Iterator用于遍历集合
iterator.hasNext()可以判断是否有下一个元素,iterator.next()可以获取下一个元素。
- public class Main {
- public static void main(String[] args) {
- Collection col = new ArrayList();
- col.add(new Book("三国演义","罗贯中",10.1));
- col.add(new Book("小李飞刀","古龙",5.1));
- col.add(new Book("红楼梦","曹雪芹",34.6));
- Iterator iterator = col.iterator();
- while(iterator.hasNext()){//判断迭代器是否为空
- Object obj = iterator.next();
- System.out.println(obj);
- }
- iterator = col.iterator(); //重置迭代器
- while(iterator.hasNext()){
- Object obj = iterator.next();
- System.out.println(obj);
- }
- }
- }
-
- class Book{
- private String name;
- private String author;
- private double price;
- public Book(String name, String author, double price) {
- this.name = name;
- this.author = author;
- this.price = price;
- }
- public String getName() {return name;}
- public void setName(String name) {this.name = name;}
- public String getAuthor() {return author;}
- public void setAuthor(String author) {this.author = author;}
- public double getPrice() {return price;}
- public void setPrice(double price) {this.price = price;}
- @Override
- public String toString() {
- return "Book{" +
- "name='" + name + '\'' +
- ", author='" + author + '\'' +
- ", price=" + price +
- '}';
- }
- }
语法如下:
- for(元素类型 元素名:集合名或数组名){
- 访问元素
- }
增强for的底层仍然是迭代器,能够遍历集合和数组。
List接口基本介绍:
1.往list集合中添加的元素是有序的,可以理解为队列,先进先出。
2.list集合里面的元素可以重复。
方法:
list.indexOf(对象名称) 可以返回该对象所在位置
list.add(下标,对象名称) 可以在下标的位置插入对象,原本的对象后移
list.set(下标,对象名称) 可以将下标位置的元素改为新的对象
list.subList(开始位置下标,结束位置下标) 可以返回开始到结束的对象,前闭后开,取不到结束位置
- public class Main {
- public static void main(String[] args) {
- List list = new ArrayList();
- list.add(new Books("红楼梦","曹雪芹",100));
- list.add(new Books("西游记","吴承恩",10));
- list.add(new Books("水浒传","施耐庵",9));
- list.add(new Books("三国演义","罗贯中",80));
- sort(list);
- for (Object o :list) {
- System.out.println(o);
- }
- }
- public static void sort(List list){
- for(int i=0;i<list.size()-1;i++)
- for(int j=0;j<list.size()-1-i;j++){
- //取出对象Book
- Books books1 = (Books) list.get(j);
- Books books2 = (Books) list.get(j + 1);
- if(books1.getPrice()> books2.getPrice()){
- list.set(j,books2);
- list.set(j+1,books1);
- }
- }
- }
- }
-
- class Books{
- private String name;
- private String author;
- private double price;
- public Books(String name, String author, double price) {
- this.name = name;
- this.author = author;
- this.price = price;
- }
-
- public String getName() {return name;}
- public void setName(String name) {this.name = name;}
- public String getAuthor() {return author;}
- public void setAuthor(String author) {this.author = author;}
- public double getPrice() {return price;}
- public void setPrice(double price) {this.price = price;}
- @Override
- public String toString() {return "名称:"+name+"\t\t价格:"+price +"\t\t作者:"+author;}
- }
ArrayList里面可以放入空值。
ArrayList是线程不安全的,因为没有synchronized关键字修饰。
在多线程下可以使用vector。
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)
分析使用无参构造器,创建和使用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显示的数据是简化后的,如果希望看到完整的数据,需要设置。
下图是调用有参构造器,对数组初始化,进行扩容的情况:实则是创建了一个指定大小的elementData数组。
如果是有参数的构造器,扩容机制:
1.第一次扩容就按照elementData的1.5倍扩容。
2.整个执行的流程还是和前面一样。
Vector带有synchronized是线程安全的。
单线程操作的情况用ArrayList,多线程操作的情况用Vector。
Vector无参时大小默认为10,扩容是按2倍进行扩容。
首先进行装箱:
‘’
下面这个方法添加数据到集合:
确定是否需要扩容,elementData.length为当前数组大小,minCapacity为当前存入元素的个数。
如果需要的数组大小不够用就扩容。第1次扩容时capacityIncrement为0,因此走的是oldCapacity这条,而oldCapacity一开始为10,因此newCapacity为20。
特点:
1.LinkedList底层实现了双向链表和双端队列的特点。
2.可以添加任意元素(元素可以重复),包括null。
3.线程不安全,没有实现同步。
LinkedList的底层结构:
1.LinkedList底层维护了一个双向链表。
2.LinkedList中维护了两个属性first和last分别指向首节点和尾节点。
3.每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表。
4.所以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(下标) 可以得到下标中的对象。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。