前言
身为一个软件工程的学渣,每每到了12周左右,就得经历一次连续熬夜的洗礼。平时上课又不认真听课,总是在玩手机,这不,现在好了,考试和课设全都挤在了一起,搞得人都傻了。
因为平时也没学什么技术,像那些学过前后端的同学,熟练的使用各种框架,这个课设一下子就做完了。而我这个什么都不会的人,只能一点点手撸代码,大部分都接近原生,会用struts2和hibernate还是因为作业要求才学的,哈哈哈,下面展示的时候可能使用不规范的话,还请见谅。写这篇文章算是对做完课设的一个总结吧,希望能帮到同是咸鱼的同学们。哈哈哈!
1、前端界面的架构设计
设计灵感
毕竟我是学Android方向的,Android总是要讲究优化的,同样是写界面代码,从一上手我就在想(我不会用框架0.0),有没有某种方式可以提高界面的利用率或者提高界面独立性。
于是找到了与Android APP的底部导航栏类似的东西,他是html中的标签iframe。
这里解释一下,APP的底部导航栏其实就是Fragment和按钮的配合形成的,即每次点击按钮的时候切换Fragment。而我的关注点就是,用了Fragment之后,我可以实现把每一个界面当成一个独立的界面的去开发,要用的时候就才放进去,那html可不可以也实现同样的操作,让我独立的编写每一个完整的html,等要用的时候才把这个html放进去,然后不影响每一个html独立的js响应。答案当然是可以的。
这样做的好处是:
1.代码整体井然有序,在增加页面的时候不会影响其他页面的代码,依赖性小,该页面出现bug也不会影响其他的页面的运行。
2.每个页面都是按完整html去实现,调试和维护的时候很方便
整体设计
有了iframe这个家伙后,我就可以安心的开始码前端页面的代码了,于是我把整体的界面设计成这个样子。
为了保持界面较高的复用率,我将前端界面的导航栏(CommonFrame)和顶部栏(TopFrame)固定,内容主体部分通过切换嵌入,切换由导航栏控制。这样一来,这个东西就跟Android的底部导航栏很像了。
代码演示
类似Android的主活动,用了这种实现方式后,前端也有一个主html了
//省略头部
<body>
<iframe id="topframe" src="top.jsp" width="100%" height="50px" ></iframe>
<iframe id="leftframe" src="common.jsp" width="200px" height="100%"></iframe>
<iframe id="contentframe" src="home.jsp" onload="changeFrameWidth()" height="100%" ></iframe>
</body>
然后在导航栏CommonFrame中,我们就要触发js点击事件来切换ContentFrame中的内容。
这里注意一下,跟Android中的Fragment与Fragment的通信一样,一个Frame要控制另一个Frame的内容,一般都需要有中间者,而这里的中间者就是父布局,也就是index.html咯0.0
在leftframe中触发下面的js代码就可以更改contentframe的页面
window.parent.document.getElementById(‘contentframe’).src = “xxx.jsp”
导航栏我是用ul li简单弄的,每次点击触发点击事件就行了
具体的前端页面可以参见下面的git图
2、后端分层的架构设计
这里相信大家学过框架之类的后会发现,框架已经给你封装得很好了,你在使用的时候已经就是按它原本设计好的分层方式去书写逻辑了,所以我才常常会感慨,你现在学的东西都是别人几年前都已经在用而且用得比你还好0.0,然后心生压力。
包结构
这里给大家看一下我的项目包结构
采用J2EE,即客户端页面为JSP,服务器端业务逻辑采用Java语言实现,实体类为JavaBean,Web服务器采用tomcat7.0,用struts2搭建,数据库使用hibernate映射。
表示层:采用Java Web开发技术。
控制层:采用Struts的Action组件。
业务层:封装业务逻辑(业务JavaBean)。
数据访问层:负责访问数据库,处理事务(数据访问JavaBean)。
数据库存储层:MySQL数据库。
层与层之间的调用关系
这里用一张图简单概括一下
代码演示
表示层的话,我们在前面已经介绍过了,这里就不再赘述。
控制层的话,每一个操作会对应一个action,我看了网上别人的项目,它对action的处理是在action中加了很多个ifelse判断,让一个action可以处理一个模块很多个操作。但是我感觉如果业务逻辑多了的话,这些action类最后会变得很臃肿,维护起来挺不方便的,每次增删改查都要往里面添加,所以我最后还是决定让一个action对应一个操作,所以我的action数量虽然多,但是代码里很少,名字起好也容易区别。
这里随便展示一个
public class GetGoalByTargetId extends ActionSupport {
private CourseBuilderService service = new CourseBuilderService();
@Override
public String execute() throws Exception {
ActionContext ac = ActionContext.getContext();
NetUtils.sendEntitys(ac,service.getGoalByTargetID(NetUtils.getJsonStrings(ac)));
return NONE;
}
}
数据交互我后面会讲,因为很多重复代码,我就把重复代码放到utils包里面了,所以每一个action看起来就这么简洁。
业务层就是对应每一个service,一般在service里面会依赖DAO层的对象,一个DAO对象基本上会处理一个表的增删改查,如果某一个需求的业务太复杂的话,可以进行单表查询后,在业务层进行多表关联。
public class CourseBuilderService {
TargetSupportDAO targetSupportDAO = new TargetSupportDAO();
/**
* 插入目标支撑度
*/
public void insertGoalSupport(List<String> content){
if (content != null){
TargetSupportPO targetSupportPO = new TargetSupportPO();
targetSupportPO.setGoal_id(Long.parseLong(content.get(0)));
targetSupportPO.setTarget_id(Long.parseLong(content.get(1)));
targetSupportPO.setSupport(Double.valueOf(content.get(2)));
targetSupportDAO.addTargetPO(targetSupportPO);
}
}
}
数据访问层对应DAO包下的内容,这里要求每一个DAO类对应每一个表的增删改查的操作,因为项目不是很复杂,所以我没有对DAO进行抽象成接口,正常开发好像是要有这个操作,你们注意下就行。虽然用了hibernate框架,但是可能我才疏学浅,没能发现它的好用之处0.0(在做课设的时候,这个家伙把我搞得挺难受的,遇到各种莫名其妙的bug,哎不提了),所以我还是喜欢用原生sql去查询,多表查询调试完一串sql放进去就出结果,哈哈哈,这感觉是特别爽。当然简单的单表查询我还是用hql的,毕竟可以省去类转换的操作。(注意原生sql的查询得到的类是object对象数组,这个bug搞了我很久,让我学会了调试技能0.0,f8都快给我按烂了)
这里展示的是hibernate中原生sql写法,出现斜杆也没关系,使劲撮,可以用的
query.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
是数据封装成map方便根据列名获取数据
/**
* 根据课程名、目标名和版本号查找个人达成度
* content.get(0) ===> 课程名
* content.get(1) ===> 目标名
* content.get(2) ===> 版本号
* @return {ps(个人达成度)}
* @throws HibernateException
*/
public List findPersonalAchievement(List<String> content,String no) throws HibernateException{
Session session = hsp.getSession();
Transaction tx = session.beginTransaction();
SQLQuery query = session.createSQLQuery("SELECT SUM(course_assess.Weight * ds.personSc) as ps FROM course_assess,(\n" +
"SELECT dt.Course_Assess_ID,grade.StuNo,SUM(grade.Score)*(CASE \n" +
"\tWHEN Weight > 1 THEN\n" +
"\t\t1\n" +
"\tELSE\n" +
"\t\tWeight\n" +
"END\n" +
") as personSc FROM grade,(\n" +
"SELECT * FROM details WHERE details.Goal_ID = (\n" +
"\tSELECT goal.ID FROM goal WHERE goal.Course_ID = (\n" +
"\tSELECT course.ID from course,version \tWHERE course.Name = '"+content.get(0)+"' AND course.Version_ID = version.ID AND version.Year = '"+content.get(2)+"'\n" +
"\t) AND goal.num = '"+content.get(1)+"'\n" +
") \n" +
") as dt WHERE dt.ID = grade.Detail_ID and grade.StuNo = '"+no+"' GROUP BY dt.Course_Assess_ID\n" +
") as ds WHERE ds.Course_Assess_ID = course_assess.ID GROUP BY ds.StuNo");
query.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
List list = query.list();
tx.commit();
session.close();
return list;
}
数据库存储层的话就不用说了吧,用啥数据库,就在hibernate.cfg.xml配置文件中改一下就好了,上课会讲的,注意听0.0
3、前后端的数据交互
数据交互了,经过debug的跌打滚打,发现了request和respond类中的一个属性是用来存值的(debug调试是真的香-。-),于是,数据交互就使用了流的形式去交互。(实不相瞒,在我发现这个欣喜之余,我同学走过来看了下,告诉我说是他们学习后台视频就有的,我裂开了0.0哈哈哈,我太菜了)
主要是形式是前端使用ajax,传输数据封装成Json格式,然后在后台action中再把数据获取到,解析成实体类去使用
代码实现
js代码
看看js代码,原生写太冗余了,所以用了JQuery来写ajax
这个固定写法挺好了,每次只要改了data的内容还有url地址,就可以传递不同的地方了,success中的函数返回的时候后台传来的响应内容,通过查询得到的数据一般可以在这里处理。
如果你们有心的话后,可以再对他进行封装成一个方法,写成观察者模式也行。
//获取表单数据
function getData() {
var data = {
courName: $("#courName").val(),
selectGoal: $("#selectGoal").children('option:selected').text(),
selectVersion: $("#selectVersion").children('option:selected').text()
}
return data;
}
function getInfo() {
// 根据三种输入情况更新表格
var data = getData();
$.ajax({
url: "getOverallAchievement.action",//获取总体达成数据
type: "POST",
data: data,
dataType: "json",
contentType: "application/x-www-form-urlencoded",
success: function (msg) {***省略***}})
}
java代码
后台java代码的处理会分两个步骤,一个是接受json数据解析,一个是封装成json数据发送,这里把代码放这里大家可以参考下。解析的数据我用一个集合去存放了,对了上面那个data的值,按顺序存而已。
//这个是action中的代码
@Override
public String execute() throws Exception {
ActionContext ac = ActionContext.getContext();
List<String> jsonStrings = NetUtils.getJsonStrings(ac);
NetUtils.sendEntitys(ac,service.getPersonalAchievementInfo(jsonStrings));
return NONE;
}
//用到两个方法在下面,我是放到utils中的
public static List<String> getJsonStrings(ActionContext ac) {
HttpServletRequest request = (HttpServletRequest)ac.get(StrutsStatics.HTTP_REQUEST);
Map<String,String[]> jsondata = request.getParameterMap();
Set<String> strings = jsondata.keySet();
List<String> content = new ArrayList<>();
for (String str:strings
) {
content.add(jsondata.get(str)[0]);
}
return content;
}
public static <T> void sendEntitys(ActionContext ac,T entity) throws Exception {
HttpServletResponse response = (HttpServletResponse) ac.get(ServletActionContext.HTTP_RESPONSE);
response.setContentType("text/html;charset=utf-8");
Gson gson = new Gson();
response.getWriter().write(gson.toJson(entity));
}
4、遇到其他的问题
4.1 URL有中文要怎么保留
前端js使用二次编码encodeURI(encodeURI(url));
后台接收的时候使用一次译码URLDecoder.decode(“chinese string”,“UTF-8”)
这样如果要get请求添加参数的话,就不会出现中文乱码了
4.2 Action不一定非要return转发一个页面,可以return null
@Override
public String execute() throws Exception {
return NONE;
}
struts.xml的action标签就不用添加result了
<action name="testAction" class="com.XiaoTou.action.testAction"></action>
4.3 缩放网页导致ContentFrame内容变形
解决方法
<iframe id="contentframe" src="home.jsp" onload="changeFrameWidth()" height="100%" ></iframe>
//改变
function changeFrameWidth(){
var ifm= document.getElementById("contentframe");
ifm.width= document.body.clientWidth -205 //这个205可以调试改变
}
window.onresize=function(){
changeFrameWidth();
}
4.4 action跳转的时候找不到静态css等的资源
在struts2配置文件中package标签加入 namespace="\"
<package name="PCMS" extends="struts-default" namespace="/">
4.5 hibernate进行更新的时候不能漏了executeUpdate()
不然会出现不报错但是没结果0.0,搞死你哟
session.createSQLQuery(mySql).executeUpdate();