Icebound

icebound-area

2019小学期作业反思

低下头,看看自己有多么菜

瞎写的

这次小学期真的是让我身心俱疲。在睡了将近十二个小时后,我爬起来上了两节互联网协议,回到宿舍,看了看斗鱼时刻,终于下定决心,反思一下我这个菜鸡在这次小学期中是怎么把自己菜死的。

总结起来就两件事:过分高估自己,低估别人。

小学期代码已经全部传上github。https://github.com/iceboundx/Lumweb

开始

这次小学期我们专业还是做一个网站。这个网站使用的框架不限,老师推荐了一种上古的方法:jsp+servlet。。。。我看了看现在各种技术,选了个最傻逼的servlet做后端(如果要是当时不想着挑战自己选个spring boot早弄完了)

我有两个队友,都是很好的人,他们之前没接触过前后端,我就让他们去写前端的静态网页了,后端我实现restful之后前端用js拼接一下就行了(不要问我为什么不用模版引擎,因为我当时不知道有这种东西)。

数据库选了mysql,配合c3p0数据库连接池使用。

总之就这样,我们就开始干了。

结束

昨天刚刚验收完,得分不高,主要原因还是前端太丑了。

其他同学的网页基本上都是套的模板,前端的html模板配合jsp生成页面,难度非常低,但是一眼看出来的效果还不错。碰巧验收我们的老师似乎不太懂,我给他讲了我们的nginx+tomcat的服务器结构和前后端分离的restful api,他都没有什么反应,反而说我们数据库表太少。。。我们后一个组依靠着帅气的模板和一个视频成功俘获了老师的心,后来我还看到一个组后端套了个python机器学习的模块唬住老师。。。真是失败呀。。

前端

bootstrap

前端我们用了bootstrap的框架,大体上实现了部分页面的自适应和页面美化。

bootstrap最强的是它的栅格布局,其他控件没什么太多意思。栅格布局这里不多说,官网都有。另外他的navbar可以根据屏幕大小自动收缩也非常有意思。

jquery

jquery最常用的就是$(‘#id’)通过id选中某个元素,其他也都有文档,基本上html代码都能改。我这里ajax也是通过jquery实现的

function get_data()
{
    $.ajax({
        type: "GET",
        url: "product.do/list",
        dataType: "json",
        data:{
            page:now_page+1,
            num:8
        },
        success: function(result){
            now_page++;
            add_page(result,now_page,result.length);
         },
         error: function(){
            toastr.error("server error");
         }
     });
}

然后因为拼接界面我没有用模板引擎,我用了jquery直接拼接。。这太蠢了。。

var now_page=0;
var total_num=0;
var proitem=['<div class="col-md-3">',
'<div class="goods-item up-hover-shadow" onclick="todetail(this);" >',
'		<div class="figure-img">',
'		<img src="images/2.png" width="200" height="200" alt=""> ',
'		</div> ',
'		<h3 class="title">LumDogⅤ</h3> ',
'		<p class="price">£17.98</p> ',
'		<div class="flags"><span class="glyphicon glyphicon-fire" style="color:Red;font-size:20px;" aria-hidden="true"></span></div>',
'		</div>',
'		</div>'].join("");
var rowleft=['<div class="row" id="'].join("");
var rowright=['"></div>'].join("");
var proitemleft=['<div class="col-md-3">',
'<div class="goods-item up-hover-shadow" onclick="todetail(this);" id="'].join("");
var proitemright=['">		',
'<div class="figure-img">		',
'<img src="images/loading.gif" width="200" height="200" alt=""> </div> 		',
'<h3 class="title">loading...</h3> 		',
'<p class="price">loading...</p>',
'<div class="flags"></div></div></div>'].join("");
var hotitem=['<span class="glyphicon glyphicon-fire" style="color:Red;font-size:20px;" aria-hidden="true"></span>'].join("");
var nohotitem=['<span class="glyphicon glyphicon-leaf" style="color:White;font-size:20px;" aria-hidden="true"></span>'].join("");
function add_item(product,rownum){
    var nowrow='#row'+rownum;
    var nowitem='#pro'+product.pid;
    $(nowrow).append(proitemleft+"pro"+product.pid+proitemright);
    $(nowitem).find('img').attr("src",'images/product/thum'+product.pid+'.jpg');
    $(nowitem).find('.title').text(product.name);
    $(nowitem).find('.price').text('£'+product.price);
    if(product.state=="hot")$(nowitem).find('.flags').html(hotitem);   
    else $(nowitem).find('.flags').html(nohotitem);    
}
function add_page(product_array,page_num,tot){
    var rn=Math.ceil(tot/4);
    var now_row=(page_num-1)*2;
    var now_item=0;
    for(i=1;i<=rn;i++){
        add_row(now_row);
        for(j=1;now_item<tot&&j<=4;now_item++){
            add_item(product_array[now_item],now_row);
        }
        now_row++;
    }
    endLoad();
}

以上代码建议大家不要学。。。。太蠢了,推荐大家使用模板引擎

这里说一个问题.val和.attr(‘value’,’xxx’)是不一样的。.val是实际的值,.attr(‘value’,’xxx)是更改的默认值。一开始这里被坑了。

html5

html5有很多新特性,这里用到一个表单检测输入是否合法的。

<input type="tel" required="required" pattern="[0-9]{11}" oninvalid="setCustomValidity('illegal input!')" oninput="setCustomValidity('')" placeholder="mobile phone number" name="u">

pattern里写正则,oninput写正在输入时的消息,oninvalid写错误信息。

后端

java

后端我分了四层和一个工具包

dao为持久层,负责与数据库通信,只有简单逻辑,其中dao.impl包为实现。
domain为数据模型层,其中的类均为javabean,定义了基本的数据模型。
service为服务层,该层可以调用多个dao层,负责复杂逻辑与服务处理。
web为网络层,根据请求不同选择不同的service层,负责网络通信。
utils为工具包,提供了各层公用的一些工具方法。

事实证明这样写一点卵用没有,我要是dao层出了bug,就可能要一路从底改到顶部(呲牙)。。。不过好在我代码水平还可以,dao层和service层基本没什么问题。

web层里都是httpservlet对象, utils里面我抄了别人的一个md5加密,序列化/反序列化用的fastjson。

数据库

数据库我本地用的mysql 8.0,服务器上用的MariaDB 10.2.11,这导致一个非常尴尬的事情,我本地的mysql workbench连上服务器上的MariaDB之后无法直接把数据导回本地,还要手动输入命令导出。

数据库连接池我直接使用了比较流行的c3p0,配置连接器的时候要注意一下,高版本的mysql需要考虑时区问题。这里我用了jdbc driver。配置要这么写

<property name="jdbcUrl">
jdbc:mysql://localhost:3306/lumweb&useSSL=false&serverTimezone=Asia/Shanghai
</property>

如果在中国,强烈建议时区写成上海,要不然时间会错乱。。

c3p0配置方面,注意设置maxIdleTime为3000或者5000之类的数字,要不然会出现连接过期,但是连接依然存在于连接池中的问题。

java代码方面,我用了org.apache.commons.dbutils这个包里的一部分方法,配合javabean真的非常非常好用。下面我详细写一下。

首先是建立一个工具类负责使用数据库连接池:

package utils;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
 
import javax.sql.DataSource;
 
import com.mchange.v2.c3p0.ComboPooledDataSource;
 
public class JdbcUtils {
 
	private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
	public static DataSource getDataSource() {
		return dataSource;
	}
	public static Connection getConnection() {
		try {
			return dataSource.getConnection();
		} catch (SQLException e) {
			throw new RuntimeException(e);
			}
	}
	public static void release(Connection con, PreparedStatement par, ResultSet re) {
		if (re != null) {
			try {
				re.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if (par != null) {
			try {
				par.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if (con != null) {
			try {
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

然后这里是一段读取全部商品信息,并且保存到一个ArrayList的代码,还有一个添加商品信息的:

package dao.impl;
 
import java.sql.SQLException;
import java.util.ArrayList;
 
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
 
import dao.ProductDao;
import domain.Product;
import utils.JdbcUtils;
 
public ArrayList<Product> getAll(int start, int end) {
	try {
		QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
		String sql = "select * from product order by state,pid limit ?,?";
		return (ArrayList<Product>)runner.query(sql, new BeanListHandler<Product>(Product.class), start, end-start+1);
	} catch (Exception e) {
		e.printStackTrace();
		throw new RuntimeException(e);
	}
}
public boolean addPro(Product product) {
	if(existPro(product.getPid()))return false;
	try {
		QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource());
		String sql = "insert into product values(?,?,?,?,?,?)";
		Object params[] = {product.getPid(),product.getName(),product.getPrice(),product.getStock(),product.getShortdesc(),product.getState()};
		runner.update(sql, params);
		return true;
	} catch (SQLException e) {
		e.printStackTrace();
		throw new RuntimeException(e);
	}
}

配合fastjson真的好用。。当然比不上spring boot

服务器配置

服务器我就用的是这台阿里云,9.79一个月,学生认证,注意每个月单独买比包年便宜几块钱。。

因为我一直懒得更新,服务器目前还是debian 9。java装的11.0.4,tomcat也是用的最新版本,nginx是1.12.1(懒得更新)。

为了挑战(zhuang)自己(B),我使用nginx访问静态资源,tomcat访问动态资源。

因为我的nginx上运行着这个博客,所以需要新建了一个vhost,里面配置文件如下:

server
    {
        listen 2333;
        #listen [::]:2333;
        server_name 60.205.183.171;
        index index.html index.htm;
        root  /home/wwwroot/lumweb;
 
        error_page   404   /404.html;
 
        # Deny access to PHP files in specific directory
        #location ~ /(wp-content|uploads|wp-includes|images)/.*\.php$ { deny all; }
 
		location ~.*\.xml {
		   deny all;
		}
 
        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|html)$
        {
            expires      10d;
        }
 
        location ~ .*\.(js|css)?$
        {
            expires      12h;
        }
 
	location ~ /*.do
	{
	    proxy_pass  http://localhost:8080;
	}
 
 
	location ^~  /WEB-INF
	{
	    deny all;
	}
 
	location ^~  /META-INF
	{
	   deny all;
	}
 
    location ~ /\.
    {
      deny all;
    }
    access_log off; 
}

1.把这个文件放在conf/vhost/里即可。然后去修改tomcat的配置文件。因为我们这个server只运行这一个网站,所以我们需要去除tomcat自带的路径名。

2.此外,我这个网站支持上传图片到webroot文件夹中,所以我们要更改网站的根目录到 /home/wwwroot/lumweb中,并把tomcat的整个webroot文件夹复制到/home/wwwroot/lumweb(包括WEB-INF和META-INF)。

在conf/server.xml中末尾处修改Hostname里的配置为:
<Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">
<Context path="/" docBase="/home/wwwroot/lumweb" reloadable="false"></Context>
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
 </Host>

3.这里还有另一个问题,新版tomcat上传的文件没有others的读取权限,这里也要改一下bin/catalina.sh中的unmask参数

# Set UMASK unless it has been overridden
if [ -z "$UMASK" ]; then
    UMASK="0022"
fi
umask $UMASK

随后在bin目录中使用./startup.sh运行tomcat,并同时启动nginx即可。我这里选择的端口是2333端口,如果你使用的是阿里云服务器,一定记住在服务器安全组配置中打开2333端口。

反思

我这次被人干爆,主要原因是侧重点选错了。这小学期其实就是一个过家家游戏,游戏就要有游戏规则。我这次一开始就选定了自己撸前端,不使用现成的html模板的方式,由于我们没有美工,整个前端界面稀烂,使用体验还行,就是外观极差,老师看到都要呕。而后端虽然做了很多提高效率的方法,但由于没有并发,最后根本看不出来效果。还不如用jsp随便撸一个。

昨天npy和我在教三楼谈了很久,有几个观点我很认同。

第一个是我肯定是菜,因为如果我足够牛逼,首先这个网页根本没难度,其次我可能根本不需要跟那些jsp一起比,既然我已经和他们在同一个学校了,代表我已经和同专业其他人处于一个层次,我不能遗世独立,超脱世外(况且我还考不过他们),更不能轻视任何一个人,别人也不是傻子。

第二是我不能过分重视这个成绩。因为他没有办法反映参与者的真实水平,最后验收的时候,每个老师打分标准都不一样,导致成绩的随机性很高,电商法的Bindu老师根本什么都不懂,搞得很多同学很难受。

总而言之,这是我第一次接触这些框架。搞竞赛的时候特别瞧不起做web开发的,现在接触了django、spring boot,我改变了一些看法,很佩服搞出这些轮子的人,也不再认为web开发是个垃圾职业了。虽然我还是认为开发岗,尤其是前端岗,本身跟搬砖没什么区别。

  1. 櫻川 浅羽说道:

    仔細看了半天,纔從字縫裏看出字來,滿篇都寫着兩個字是「不值」!