在线协作编辑器之周报收集
一、实验说明
下述介绍为实验楼默认环境,如果您使用的是定制环境,请修改成您自己的环境介绍。
1. 环境登录
无需密码自动登录,系统用户名shiyanlou
2. 环境介绍
本实验环境采用带桌面的Ubuntu Linux环境,实验中会用到桌面上的程序:
- LX终端(LXTerminal): Linux命令行终端,打开后会进入Bash环境,可以使用Linux命令
- Firefox:浏览器,可以用在需要前端界面的课程里,只需要打开环境里写的HTML/JS页面即可
- Eclipse:Eclipse 是著名的跨平台的自由集成开发环境(IDE)。主要用来 Java 语言开发,但是目前亦有人通过插件使其作为 C++ 和 Python 等语言的开发工具
3. 环境使用
使用GVim编辑器输入实验所需的代码及文件,使用LX终端(LXTerminal)运行所需命令进行操作。
实验报告页面可以在“我的主页”中查看,其中含有每次实验的截图及笔记,以及实验的有效学习时间(指的是在实验桌面内操作的时间,如果没有操作,系统会记录为发呆时间),这些都是您学习的真实性证明。
二、课程介绍
1. 实验内容
大家在工作和学习中时常会遇到多人编辑一个文件的情况;那么多人在线协作文档编辑器将是一个很实用的工具,适合小组内的文档编辑,例如可用于小团队内部进行实时编写和收集周报等。
课程通过分析项目要实现的功能,调研目前的技术现状,设计了该课程来实现该功能,课程使用java语言开发,应用非常流行的springmvc框架,引入ckeditor插件,并加入localStorage缓存技术,最终使用Eclipse开发工具完成。
项目不太难,非常适合JAVA或其他爱好者进行共同学习,只要有一些 Java Web开发基础的同学都会看懂的!
2. 实验知识点
- ckeditor (在线的编辑器) 【重点掌握】
- localStorage (HTML 5 Web 存储)【重点掌握】
- springmvc (轻量级Web框架) 【需提前了解java web的springmvc框架】
- spring jdbcTemplate 【需提前了解java web的spring框架】
- mysql 数据库 【需了解mysql的创建数据、创建表】
3. 源码下载
本课程提供源码下载地址,建议大家根据实验步骤一步一步完成,将收获更大;
4.项目效果截图
代码开发完成后,在eclipse中启动项目后,打开火狐浏览器,输入 localhost:9090/index ,将出现本节实验的效果图,如下:
三、实验原理
1、待实现的功能需求
序号 | 需求名称 | 需求详述 |
---|---|---|
1 | 在线协作编辑 | 嵌入浏览器网页的在线编辑器,支持多人协作编辑,刷新后读取后台最新编辑保存的内容 |
2 | 实时自动保存 | 通过在线编辑器,输入的内容,能够实时自动保存到客户端;刷新后数据不丢失;点击提交能够保存到数据库中 |
2、技术调研
1 在线协作编辑
实现多人在线编辑的功能,可自行开发web编辑器插件,但是实现成本较高,经调研目前已经有很多在线编辑器插件可以调用,请参靠:
HTML编辑器-HTML网页表单可视化在线编辑器插件大全
其中,CKEditor(原FCKEditor)是一个现成的使用旨在简化Web内容创建HTML文本编辑器。是国外比较流行的网页文本在线编辑器,早期DEDECMS管理后台发布内容地方使用此编辑器,这是一个所见即所得的编辑器,带来了共同的文字处理器的功能,直接到您的网页。
最后,因为CKEditor免费开源、完全可定制、高标准的质量等优点,该课程选择该插件作为前端的输入。
2 实时自动保存
web缓存技术,涉及内容很多;可参看
最终,为了兼顾数据保存的简单高效和安全,我们选用 sessionStorage作为前端存储,因为sessionStorage的优点就是方便高效;同时为了保证数据的安全不丢失,我们在用户确认编写信息无误后,通过触发按钮的方式将数据提交后台,交由服务器进行存储,因为服务器存储数据安全性高。
3、系统设计
四、开发准备工作
1 新建项目
双击桌面的eclipse图标,打开eclipse软件,点击工具栏FILE-New-Dynamic Web Project,进入新建java web项目页面;新建 Dynamic Web Project,命名为 WeekReport,然后点击 Next,
第二次点击 Next 按钮会进入如下所示的步骤,注意勾选生成 web.xml 选项。
2 导入jar包
先下载依赖的jar包,下载地址url为http://labfile.oss.aliyuncs.com/courses/742/lib.zip。
$ wget url
$ unzip lib.zip
$ cp lib/* /home/shiyanlou/workspace/WeekReprot/WebContent/WEB-INF/lib
将jar包解压之后,将所有的jar包文件,全部都复制到WeekReport项目工程下的 WebContent/WEB-INF/lib 目录下面即可(jar文件目录截图如下)。
3 搭建springmvc框架
包含三步,
- 增加必要文件及其目录;【增加service、controller、dao、model目录等】
- 修改web.xml配置;【配置了listener和servlet】
- 增加spring、spring-mvc和数据库jdbc配置文件。
该部分内容不是本文的重点,如有疑问可以参考如下文章,
http://www.cnblogs.com/xing901022/p/5240044.html ;或者在课程问答里进行提问解答。
a) 在src下新建一个包,包名自定义,该实验此处我命令为:com.zn.wr。
然后在该包下,分别新建4个文件夹,service、controller、dao、model;供后续服务器端保存数据时使用。
b) 修改web.xml,配置了spring监听和servlet。为了是增加spring和spring-mvc配置文件,使项目完成spring-mvc的框架。
<!-- 增加spring监听-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext.xml</param-value>
</context-param>
<!-- springmvc文件配置在这里 -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-mvc.xml</param-value>
<!-- 上面是放在 resources下, 也可以配置在 /WEB-INF/spring-mvc.xml -->
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
c) 增加配置文件
在 WebContent\WEB-INF新建3个配置文件applicationContext.xml,spring-mvc.xml,jdbc.properties。
applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 配置jdbc文件 -->
<context:property-placeholder location="/WEB-INF/jdbc.properties" />
<!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
<context:component-scan base-package="com.zn.wr.controller" />
<context:component-scan base-package="com.zn.wr.dao" />
<!-- 配置Jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass">
<value>${jdbc.driverClassName}</value>
</property>
<property name="jdbcUrl">
<value>${jdbc.url}</value>
</property>
<property name="user">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
<!--连接池中保留的最小连接数。 -->
<property name="minPoolSize">
<value>5</value>
</property>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize">
<value>30</value>
</property>
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize">
<value>10</value>
</property>
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime">
<value>60</value>
</property>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement">
<value>5</value>
</property>
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。
如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
<property name="maxStatements">
<value>0</value>
</property>
<!--每60秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod">
<value>60</value>
</property>
<!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->
<property name="acquireRetryAttempts">
<value>30</value>
</property>
<!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试
获取连接失败后该数据源将申明已断开并永久关闭。Default: false -->
<property name="breakAfterAcquireFailure">
<value>true</value>
</property>
<!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable
等方法来提升连接测试的性能。Default: false -->
<property name="testConnectionOnCheckout">
<value>false</value>
</property>
</bean>
</beans>
spring-mvc.xml 下载地址:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 定义 SpringMVC的扫描路径 base-package的值与上述 a) 中在src下新建一个包,包名自定义 的值保持一致即可 -->
<context:component-scan base-package="com.zn.wr" />
<!-- 定义view 【jsp】映射关系 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
jdbc.properties下载地址:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/weekreport
jdbc.username=root
jdbc.password=admin
其中,本实验涉及到的如下文件需修改下。
jdbc.properties中的mysql的数据库的用户名和密码,请修改为你安装时设定的用户名和密码,即可。
3 项目文件结构
最终,项目的文件结构如下截图:
五、实验步骤
1. 前台 在线协作编辑 新建jsp
在\WebContent\WEB-INF下新建jsp文件目录,在jsp目录下新建index.jsp文件作为前台输入页面。index.jsp 页面代码如下:
<html>
<head>
<title>CKEditor Classic Editing Sample</title>
<script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="https://cdn.ckeditor.com/4.6.0-441b33b/full-all/ckeditor/ckeditor.js"></script>
<style type="text/css">
/* Minimal styling to center the editor in this sample */
body {
padding: 30px;
display: flex;
align-items: center;
text-align: center;
}
p{
text-align:right;
}
.container {
margin: 0 auto;
}
</style>
</head>
<body>
<%-- 1 添加编辑页面 --%>
<div class="container">
<form method="post">
<h2><label for="editor1">周报编辑器</label></h2>
<p><a href="javascript:void(0);" onclick="ManualSave();" >保存</a></p>
<!-- <a href="javascript:void(0);" onclick="Export();" >导出</a></p> -->
<textarea name="editor1" id="editor1"> </textarea>
</form>
</div>
</body>
</html>
在该jsp页面添加div,包含一个label、一个保存按钮和一个输入框。
另外通过引入js插件(jquery.min.js和ckeditor.js),来实现“在线协作编辑”功能。
2. 前台 在线协作编辑 编写js
通过初始化一个编辑器函数,并设置了编辑器的主题样式以及模式等,从而可以看到一个web端的编辑器,并且可以输入内容,就像在本地电脑的word中进行编辑。其中初始化函数中的相关参数设置可以去参考官网资料Ckeditor 。
- 增加实时缓存功能,采用sessionStorage的方式,将在线编辑器中输入内容进行本地缓存,保证数据刷新后不丢失;
- 添加手动保存功能,点击保存后,将编辑好的数据异步传到后台,保存到mysql数据库中;
- 刷新加载最新数据,手动保存后会自动刷新前台一次,将最新的数据加载到前台页面中;
<script>
/*
* 1. 每次进入页面前 都从后台取最新的数据到前台 【在index方法里】
* 2. 缓存数据判断 缓存为空 则将后台最新数据赋值 2 本地缓存 localStorage 自动保存
* 3. 缓存数据赋值给 texture值
* 4. 遇到 texture值变化,则自动保存
*/
$(document).ready(function () {
var editor=initeditor();
var initcontent= window.sessionStorage.getItem("comment_top");
// 初始化页面赋值
initdata(editor,initcontent);
// 触发自动保存 自动保存功能
editor.on('change', function(evt) {
window.sessionStorage.setItem("comment_top", evt.editor.getData());
});
});
function initeditor(){
var editor= CKEDITOR.replace( 'editor1' , {
toolbar: [
{ name: 'clipboard', items: [ 'Undo', 'Redo' ] },
{ name: 'styles', items: [ 'Format', 'Font', 'FontSize' ] },
{ name: 'basicstyles', items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'RemoveFormat', 'CopyFormatting' ] },
{ name: 'colors', items: [ 'TextColor', 'BGColor' ] },
{ name: 'align', items: [ 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock' ] },
{ name: 'links', items: [ 'Link', 'Unlink' ] },
{ name: 'paragraph', items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote' ] },
{ name: 'insert', items: [ 'Image', 'Table' ] },
{ name: 'tools', items: [ 'Maximize' ] },
{ name: 'editing', items: [ 'Scayt' ] }
],
height: 800 , //外面长方形 高多少
contentsCss: [ 'https://cdn.ckeditor.com/4.6.0-441b33b/full-all/ckeditor/contents.css', 'mystyles.css' ] , //有无里面的方格
allowedContent : true ,
disallowedContent: 'img{width,height,float}' ,
extraAllowedContent: 'img[width,height,align]' ,
extraPlugins: 'tableresize,uploadimage,uploadfile' ,
bodyClass: 'document-editor' ,
format_tags: 'p;h1;h2;h3;pre' ,
removeDialogTabs: 'image:advanced;link:advanced'
});
return editor;
};
function initdata(editor,initcontent){
//显示进度条代码结束
var aj =$.ajax({
type : "POST",
url : "initdata",
async: false,
dataType : "json",
success : function(data) {
if (data == null) {
alert("没查到最新的信息");
} else if (!initcontent && typeof initcontent != "undefined" && initcontent != 0) {
editor.setData(data.scontent);
}else{
editor.setData(initcontent);
}
}
});
};
//手动保存页面
function ManualSave(){
var aj = $.ajax( {
url:'save', // 跳转到 action
type:'POST',
dataType:'json',
contentType: 'application/json;chartset=UTF-8',
data:JSON.stringify(window.sessionStorage.getItem("comment_top")), // 传批量的参数 list
success:function(data) {
if(data.state){
alert("保存成功!");
window.sessionStorage.removeItem("comment_top");
window.location.reload();
}else{
alert("保存失败!");
}
},
error : function() {
alert("网络异常!");
}
});
};
</script>
3. 后台实时自动保存功能解析
在src\com\zn\wr\model下新建一个实体类Content.java用于对应前台的输入框,其中包含的字段scontent用来存储编辑的内容。Model层,用持久化类描述实例对象。
public class Content{
String scontent; // 编辑内容
public String getScontent() {
return scontent;
}
public void setScontent(String scontent) {
this.scontent = scontent;
}
在src\com\zn\wr\controller下新建一个实体类SaveController.java,用来处理在前台输入编辑内容完成后,点击保存按钮动作,触发的后台操作。Controller层,连接前台页面和后台业务,接收数据并调用Dao层操作。
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.zn.wr.dao.contentDAO;
import com.zn.wr.model.Content;
@Controller
public class SaveController {
private Content content;
@Resource
private contentDAO contentdao;
@RequestMapping("/index") // 通过spring mvc 的requestingmapping 跳转到前台index.jsp
public String toLoginPage()throws Exception {
return "index";
}
/*
* 初始化最新数据
*/
@ResponseBody
@RequestMapping(value = "initdata", method = RequestMethod.POST)
public Content toInitData(){
this.content = new Content();
this.content.setScontent(contentdao.search());
System.out.println("content:"+contentdao.search());
return content;
}
/*
* 手工保存
*/
@RequestMapping(value="save", method = {RequestMethod.POST} ,produces = "text/html;charset=UTF-8")
@ResponseBody
public Map<String, Object> doSave(@RequestBody String initcontent) {
boolean state = contentdao.insert(initcontent);
Map<String, Object> modelMap = new HashMap<String, Object>();
modelMap.put("state", state);
return modelMap;
}
}
在src\com\zn\wr\dao下新建一个实体类contentDAO.java,用于响应Controller层的操作, DAO层,数据持久化,把对象的各种操作进行封装;并将前台的数据存储到数据库中。
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class contentDAO {
@Autowired // 注入 spring jdbctemplate
private JdbcTemplate jdbcTemplate;
// 手动保存功能
public boolean insert(String content)
{
try {
String sql = " INSERT INTO w_content(content,createtime,updatetime )VALUES (?,now(),now())";
Object[] params = new Object[] { content };
jdbcTemplate.update(sql, params);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
//刷新最新数据
public String search()
{
String content = null;
try {
String sql = "SELECT content from w_content ORDER BY updatetime DESC LIMIT 1";
content = (String) jdbcTemplate.queryForObject(sql, java.lang.String.class);
} catch (Exception e) {
e.printStackTrace();
}
return content;
}
}
Service层,业务逻辑层,调用DAO层操作,此处业务逻辑简单,省略该层。
六、数据库
1 查看数据库状态
$ sudo service mysql status // 查看mysql状态
$ sudo service mysql start //如果没启动就可以启动
$ sudo service mysql stop //使用完毕之后就可以关闭
2 启动mysql数据库
启动mysql数据库,如不熟悉这部分内容,可先学习实验楼中的【SQL的介绍及MySQL的安装】这个课程。
默认实验楼环境中的mysql的root用户的密码为null,为了安全起见,需要进行修改密码。修改root密码的方法。
通过使用SET PASSWORD命令
$ mysql -u root
mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpass');
3 创建项目数据库和表
创建数据库
$ mysql -u root -p
$ mysql> create database weekreport default character set utf8 collate utf8_general_ci;
$ mysql> show databases;
$ mysql> use weekreport;
创建表的SQL
Navicat MySQL Data Transfer
Source Server : localhost_3306
Source Server Version : 50715
Source Host : localhost:3306
Source Database : weekreport
Target Server Type : MYSQL
Target Server Version : 50715
File Encoding : 65001
Date: 2016-12-21 10:47:58
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for w_content
-- ----------------------------
DROP TABLE IF EXISTS `w_content`;
CREATE TABLE `w_content` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`content` text,
`createtime` datetime DEFAULT NULL,
`updatetime` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=40 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS=1;
七、实验效果图
1 eclipse项目启动
选中server中的Tomcat V7.0 Server,然后右键点击 add and remove,
选择weeklyreport 项目,然后双击server中TomcatV7.0Server,出现如下图示,进行修改访问的port=9090(可随意定义,保证没有其他的服务使用该端口即可)
启动成功的console提示页面
2 火狐浏览器访问测试
打开火狐浏览器,输入 localhost:9090/index ,出现课程的初始效果图:
输入 “2017 first course shiyanlou 2017.2.12”等字样后,数据自动保存到缓存中,直接刷新页面,数据不丢失
3 保存之后显示最新数据
点击保存按钮,弹出保存成功字样。
点击,确定后自动刷新最新数据:
八、课后习题
- 本次项目课的内容比较多,建议多动手操作几遍,并且仔细回顾和思考,才能真正理解。
- 目前还不支持导出功能;可以考虑添加另存为word、pdf等格式的文件;还可以添加工作流、添加邮件、短信通知功能。
- 课后作业:本项目的前台代码中,js调用的是外部链接,请动手修改为本地的链接调用试试?看下效果是不是一样。
感兴趣的可以直接到实验楼去操作完成这个课程。
友情提示
我是和奇谷朴,一个在帝都周末自己选择加班的有志好青年,读完我的文章如果有收获,记得打赏、关注和点赞哦!么么哒!!