基于Nutz的CMS内容管理系统大功告成
功能比较类似于JEECMS,基于Nutz 所以二次开发非常容易,并且UI表现层完全和JEECMS不一样,主要是ajax+弹出窗口的方式。
Q-Q: 1162-4317
Q群:2631-0065 合肥Android/Java开发-GDG
运行效果:
Wizzer's Blog
功能比较类似于JEECMS,基于Nutz 所以二次开发非常容易,并且UI表现层完全和JEECMS不一样,主要是ajax+弹出窗口的方式。
Q-Q: 1162-4317
Q群:2631-0065 合肥Android/Java开发-GDG
运行效果:
StartSetup 项目启动时新建线程:
package com.hits.core; import com.hits.modules.cms.task.LoadTask; import org.nutz.log.Log; import org.nutz.log.Logs; import org.nutz.mvc.NutConfig; import org.nutz.mvc.Setup; import com.hits.common.config.Globals; import org.quartz.SchedulerException; import org.quartz.impl.StdSchedulerFactory; /** * 类描述: 创建人:Wizzer 联系方式:www.wizzer.cn 创建时间:2013-11-26 下午2:11:13 */ public class StartSetup implements Setup { private final static Log log = Logs.get(); @Override public void destroy(NutConfig config) { } @Override public void init(NutConfig config) { try { //初始化Quartz任务 Globals.SCHEDULER = StdSchedulerFactory.getDefaultScheduler(); new Thread(config.getIoc().get(LoadTask.class)).start(); } catch (SchedulerException e) { log.error(e); } catch (Exception e) { log.error(e); } } }
LoadTask.java 从任务表中加载任务,定时执行类方法:
package com.hits.modules.cms.task; import com.hits.common.action.BaseAction; import com.hits.common.config.Globals; import com.hits.modules.cms.task.bean.Cms_task; import org.apache.commons.lang.StringUtils; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.integration.quartz.NutQuartzJobFactory; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.log.Log; import org.nutz.log.Logs; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import java.util.*; /** * Created by Wizzer on 14-3-4. */ @IocBean public class LoadTask extends BaseAction implements Runnable { @Inject protected Dao dao; private final static Log log = Logs.get(); public void run() { List<Cms_task> tasks = daoCtl.list(dao, Cms_task.class, Cnd.where("is_enable", "=", 0)); Globals.SCHEDULER.setJobFactory(new NutQuartzJobFactory()); for (int i = 0; i < tasks.size(); i++) { Cms_task task = tasks.get(i); try { Map<String, String> map = new HashMap<String, String>(); if (task.getTask_type() == 2 || task.getTask_type() == 3) { map.put("site_id", task.getSite_id()); map.put("channel_id", task.getParam_value()); } else if (task.getTask_type() == 1) { map.put("site_id", task.getSite_id()); } JobBuilder jobBuilder = JobBuilder.newJob(getClassByTask(task.getJob_class())); jobBuilder.setJobData(getJobDataMap(map)); TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger(); if (StringUtils.isNotBlank(task.getTask_code())) { jobBuilder.withIdentity(task.getTask_code(), Scheduler.DEFAULT_GROUP); triggerBuilder.withIdentity(task.getTask_code(), Scheduler.DEFAULT_GROUP); } else { UUID uuid = UUID.randomUUID(); jobBuilder.withIdentity(uuid.toString(), Scheduler.DEFAULT_GROUP); triggerBuilder.withIdentity(uuid.toString(), Scheduler.DEFAULT_GROUP); task.setTask_code(uuid.toString()); daoCtl.update(dao, task); } triggerBuilder.withSchedule(getCronScheduleBuilder(getCronExpressionFromDB(task))); //调度任务 Globals.SCHEDULER.scheduleJob(jobBuilder.build(), triggerBuilder.build()); } catch (SchedulerException e) { log.error(e); } catch (ClassNotFoundException e) { log.error(e); } catch (Exception e) { log.error(e); } } if(tasks.size()>0){ Globals.SCHEDULER.start(); } } public static CronScheduleBuilder getCronScheduleBuilder(String cronExpression) { return CronScheduleBuilder.cronSchedule(cronExpression); } public String getCronExpressionFromDB(Cms_task task) { if (task.getExecycle() == 2) { return task.getCron_expression(); } else { int execycle = task.getTask_interval_unit(); String excep = ""; if (execycle == 5) {//月 excep = "0 " + task.getMinute() + " " + task.getHour() + " " + task.getDay_of_month() + " * ?"; } else if (execycle == 4) {//周 excep = "0 " + task.getMinute() + " " + task.getHour() + " " + " ? " + " * " + task.getDay_of_week(); } else if (execycle == 3) {//日 excep = "0 " + task.getMinute() + " " + task.getHour() + " " + " * * ?"; } else if (execycle == 2) {//时 excep = "0 0 */" + task.getInterval_hour() + " * * ?"; } else if (execycle == 1) {//分 excep = "0 */" + task.getInterval_minute() + " * * * ?"; } return excep; } } /** * @param params 任务参数 * @return */ private JobDataMap getJobDataMap(Map<String, String> params) { JobDataMap jdm = new JobDataMap(); Set<String> keySet = params.keySet(); Iterator<String> it = keySet.iterator(); while (it.hasNext()) { String key = it.next(); jdm.put(key, params.get(key)); } return jdm; } /** * @param taskClassName 任务执行类名 * @return * @throws ClassNotFoundException */ @SuppressWarnings("unchecked") private Class getClassByTask(String taskClassName) throws ClassNotFoundException { return Class.forName(taskClassName); } }
quartz-2.2.1 配置文件:
# Default Properties file for use by StdSchedulerFactory # to create a Quartz Scheduler Instance, if a different # properties file is not explicitly specified. # # 跳过版本检查 # org.quartz.scheduler.skipUpdateCheck=true org.quartz.scheduler.instanceName = DefaultQuartzScheduler org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false org.quartz.scheduler.wrapJobExecutionInUserTransaction = false # 用NutIoc接管Quartz的JobFactory,实现用户需要的注入功能 # org.quartz.scheduler.jobFactory.class=org.nutz.integration.quartz.NutQuartzJobFactory org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true org.quartz.jobStore.misfireThreshold = 60000 # 使用内存JobStore # org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
NutQuartzJobFactory.java 下载地址:
适用于 Nutz+SWFUpload、Nutz+plupload 、Ueditor 等控件文件上传,验证用户身份。
/** * 验证用户帐号,保存文件 * * @param tmpFile * @param filetype * @param file_password * @param file_username * @param errCtx * @return */ @At @Ok("raw") @AdaptBy(type = UploadAdaptor.class, args = "ioc:upload") public JSONObject uploadOneSave(@Param("Filedata") TempFile tmpFile, @Param("ueditor") String ueditor1, @Param("filetype") String filetype, @Param("title") String title, @Param("file_password") String file_password, @Param("file_username") String file_username, AdaptorErrorContext errCtx) { boolean ueditor = false;//是否是百度编辑器,编辑器对应的JS要做相应的修改 if ("true".equals(StringUtil.null2String(ueditor1))) ueditor = true; System.out.println("ueditor::::::::::"+ueditor); JSONObject js = new JSONObject(); if (errCtx != null) { if (errCtx.getAdaptorErr() != null) { if (ueditor) { js.put("state", errorMsg(errCtx.getAdaptorErr())); } else { js.put("error", errorMsg(errCtx.getAdaptorErr())); js.put("msg", ""); } System.out.println("js1::::::::::"+js.toString()); return js; } for (Throwable e : errCtx.getErrors()) { if (e != null) { if (ueditor) { js.put("state", errorMsg(e)); } else { js.put("error", errorMsg(e)); js.put("msg", ""); } return js; } } } if ("".equals(StringUtil.null2String(file_username)) || "".equals(StringUtil.null2String(file_password))) { if (ueditor) { js.put("state", "错误:请配置文件服务器用户名及密码!"); } else { js.put("error", "错误:请配置文件服务器用户名及密码!"); js.put("msg", ""); } return js; } Ioc ioc = new NutIoc(new JsonLoader("config/fileserver.json")); String u = ioc.get(FileServer.class, "fileserver").getUsername(); String p = ioc.get(FileServer.class, "fileserver").getPassword(); if (!u.equals(file_username) || !p.equals(DecodeUtil.Decrypt(file_password))) { if (ueditor) { js.put("state", "错误:文件服务器用户名或密码不正确!"); } else { js.put("error", "错误:文件服务器用户名或密码不正确!"); js.put("msg", ""); } return js; } if (tmpFile == null || tmpFile.getFile().length() < 10) { if (ueditor) { js.put("state", "错误:文件大小不可小于10B!"); } else { js.put("error", "错误:文件大小不可小于10B!"); js.put("msg", ""); } return js; } String filename = tmpFile.getMeta().getFileLocalName(); File file = tmpFile.getFile(); String suffixname = Files.getSuffixName(file).toLowerCase(); String ss = FileType.getSuffixname(upload, filetype); if (!ss.contains(suffixname)) { if (ueditor) { js.put("state", "错误:不允许的文件扩展名,允许:" + ss); } else { js.put("error", "错误:不允许的文件扩展名,允许:" + ss); js.put("msg", ""); } return js; } long len = tmpFile.getFile().length(); filename = filename.substring(0, filename.lastIndexOf(".")) + "." + suffixname; String date = DateUtil.getToday(); String uuid = UUID.randomUUID().toString().replaceAll("-", ""); String fname = uuid + "." + Files.getSuffixName(file).toLowerCase(); String dest = webPath(date, fname, suffixname); try { Files.move(file, new File(dest)); } catch (IOException e) { e.printStackTrace(); if (ueditor) { js.put("state", "错误:文件服务器IO异常!"); } else { js.put("error", "错误:文件服务器IO异常!"); js.put("msg", ""); } return js; } JSONObject fs = new JSONObject(); if (ueditor) { js.put("state", "SUCCESS"); js.put("original", filename); js.put("url", "/upload/" + FileType.getFileType(upload, suffixname) + "/" + date + "/" + fname); js.put("title", title); } else { fs.put("filename", filename); fs.put("filepath", "/upload/" + FileType.getFileType(upload, suffixname) + "/" + date + "/" + fname); fs.put("filesize", StringUtil.getFileSize(len, 2)); js.put("error", ""); js.put("msg", fs); } return js; } /** * 获取上传路径,根据文件类型+日期生成路径 * * @param date * @param fname * @param suffixname * @return */ public String webPath(String date, String fname, String suffixname) { String newfilepath = Mvcs.getServletContext().getRealPath( "/upload/" + FileType.getFileType(upload, suffixname) + "/" + date + "/"); Files.createDirIfNoExists(newfilepath); return newfilepath + "\\" + fname; } /** * 根据异常提示错误信息 * * @param t * @return */ private String errorMsg(Throwable t) { if (t == null || t.getClass() == null) { return "错误:未知system错误!"; } else { String className = t.getClass().getSimpleName(); if (className.equals("UploadUnsupportedFileNameException")) { String name = upload.getContext().getNameFilter(); return "错误:无效的文件扩展名,支持的扩展名:" + name.substring(name.indexOf("(") + 1, name.lastIndexOf(")")).replace("|", ","); } else if (className.equals("UploadUnsupportedFileTypeException")) { return "错误:不支持的文件类型!"; } else if (className.equals("UploadOutOfSizeException")) { return "错误:文件超出" + StringUtil.getFileSize(upload.getContext().getMaxFileSize(), 2) + "MB"; } else if (className.equals("UploadStopException")) { return "错误:上传中断!"; } else { return "错误:未知错误!"; } } }
项目部署根目录增加:crossdomain.xml 文件,解决上传控件跨域上传的问题(应用和文件服务器分开部署):
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd" > <cross-domain-policy> <site-control permitted-cross-domain-policies="all" /> <allow-access-from domain="*" /> <allow-http-request-headers-from domain="*" headers="*"/> </cross-domain-policy>
SWFUpload.prototype.setPostParams = function (paramsObject) {
this.settings.post_params = paramsObject;
this.callFlash(“SetPostParams”, [paramsObject]);
};
swfu.setPostParams({ path:$(“#path”).val() });
upload.json 配置文件,增加扩展配置项extOption,内容可以由用户自定义,返回一个Map对象。
我要实现功能是,用户在相册里只允许上传图片文件、视频里只允许上传视频格式的文件等,
结合上传控件和JS,实现在页面级和代码级的文件类型分类限制、分类保存等功能。
Nutz issue 提交需求不被接纳所以只好自己实现了,大家如需要的话可以提issue,希望以后Nutz能内置:
https://github.com/nutzam/nutz/issues/568
upload.json:
var ioc = { upload : { type : "org.nutz.mvc.upload.UploadAdaptor", args : [{refer : "uploadCtx"}] }, uploadCtx : { type : "org.nutz.mvc.upload.UploadingContext", args : [{refer: "filePool"}], fields : { ignoreNull : true, maxFileSize : 10485760, nameFilter : ".+(jpg|gif|png|jpeg|doc|docx|xls|xlsx|ppt|pptx|wps|pdf|txt|chm|mp3|mp4|3gp|rm|swf|flv|asf|wmv|wma|z|zip|rar|ios|jar)", extOption: { "images":"jpg,gif,png,jpeg", "document":"doc,docx,xls,xlsx,ppt,pptx,wps,pdf,txt,chm", "music":"mp3", "video":"mp4,3gp,rm,swf,flv,asf,wmv,wma", "archive":"z,zip,rar,ios,jar" } } }, filePool : { type : "com.hits.common.file.FilePool", args : ["/temp/", 2000] } };
改写Nutz源码:
org.nutz.mvc.upload.UploadingContext 类,增加代码:
/** * 一个扩展配置项,用户可自定义内容 */ private Map<String, String> extOption; public Map<String, String> getExtOption() { return extOption; } public UploadingContext setExtOption(String extOption) { this.extOption = Json.fromJsonAsMap(String.class,extOption); return this; }
增加一个调用类:
FileType.java
/** * 类描述: * 创建人:Wizzer * 联系方式:www.wizzer.cn * 创建时间:2013-12-25 下午1:13:30 * @version */ public class FileType { /** * 根据文件名获取文件类型,默认返回other * @param upload * @param suffixname * @return */ public static String getFileType(UploadAdaptor upload,String suffixname) { Map<String,String> hm=upload.getContext().getExtOption(); String str = StringUtil.null2String(suffixname).toLowerCase(); Set<Map.Entry<String, String>> set = hm.entrySet(); for (Iterator<Map.Entry<String, String>> it = set.iterator(); it.hasNext();) { Map.Entry<String, String> entry = (Map.Entry<String, String>) it.next(); if(entry.getValue().toLowerCase().indexOf(str)>-1) return entry.getKey(); } return "other"; } /** * 根据文件类型获取文件后缀名,默认返回nameFilter配置 * @param upload * @param filetype * @return */ public static String getSuffixname(UploadAdaptor upload,String filetype) { Map<String,String> hm=upload.getContext().getExtOption(); String str = StringUtil.null2String(filetype).toLowerCase(); if(!"".equals(str)){ Set<Map.Entry<String, String>> set = hm.entrySet(); for (Iterator<Map.Entry<String, String>> it = set.iterator(); it.hasNext();) { Map.Entry<String, String> entry = (Map.Entry<String, String>) it.next(); if(entry.getKey().toLowerCase().equals(str)) return entry.getValue(); } } String name=upload.getContext().getNameFilter(); return name.substring(name.indexOf("(")+1,name.lastIndexOf(")")).replace("|", ","); } }
使用示例:
@Inject protected UploadAdaptor upload; @At @Ok("->:/private/file/uploadOne.html") public void uploadOne(@Param("filetype") String filetype,HttpServletRequest req){ req.setAttribute("filetype", filetype); req.setAttribute("allowExtensions", FileType.getSuffixname(upload, filetype)); }
如上所述,在限制文件上传类型的同时,还可以将文件分类保存在不同类型的目录下。
由于Nutz是零配置的,所以通过URL找到处理类以及跳转的页面,就显得很麻烦,不方便维护。
于是,我在大神兽的指导下,实现如下功能:在项目启动时,将URL路径、类、方法、以及跳转页面写入项目中的一个文件中,方便查看。
@UrlMappingBy(value=UrlMappingSet.class) public class MainModule { }
在Nutz入口类,加入 @UrlMappingBy。
UrlMappingSet.java 实现在 /WEB-INF/ 目录下生成 paths.txt 文件,记录路径,文件格式如下:
UrlMappingSet.java 源码:
package com.hits.core; import java.io.File; import java.lang.reflect.Method; import org.nutz.lang.Files; import org.nutz.lang.Lang; import org.nutz.mvc.ActionChainMaker; import org.nutz.mvc.ActionInfo; import org.nutz.mvc.Mvcs; import org.nutz.mvc.NutConfig; import org.nutz.mvc.impl.UrlMappingImpl; /** * 类描述: 创建人:Wizzer 联系方式:www.wizzer.cn 创建时间:2013-12-19 下午10:36:17 * * @version */ public class UrlMappingSet extends UrlMappingImpl { private static int count = 0; public void add(ActionChainMaker maker, ActionInfo ai, NutConfig config) { super.add(maker, ai, config); printActionMappingThis(ai); } protected void printActionMappingThis(ActionInfo ai) { String[] paths = ai.getPaths(); StringBuilder sb = new StringBuilder(); if (null != paths && paths.length > 0) { sb.append(paths[0]); for (int i = 1; i < paths.length; i++) sb.append(",").append(paths[i]); } else { throw Lang.impossible(); } sb.append("\n"); // 打印方法名 Method method = ai.getMethod(); String str; if (null != method) str = String.format("%-30s : %-10s", Lang.simpleMetodDesc(method), method.getReturnType().getSimpleName()); else throw Lang.impossible(); sb.append("\t" + ai.getModuleType().getName()); sb.append("\n\r"); sb.append("\t" + str); sb.append("\n"); String filepath = Mvcs.getServletContext().getRealPath("/WEB-INF/") + "/paths.txt"; File f = new File(filepath); if (count == 0) { Files.write(f, sb.toString()); } else { Files.appendWrite(f, sb.toString()); } count++; } }
使用示例:
/** 你可以自定义取数据区间的值,如下: offset=0,count=5 取0到5的记录 offset=5,count=10 取5到15的记录 page=3, offset=15,count=20 取15到35的记录 **/ int offset=0; int count=5; ExplicitPager pager=new ExplicitPager(offset,count); QueryResult file=daoCtl.listPage(dao, Oa_doc_file.class, cri,pager);
ExplicitPager 类源码:
package com.hits.common.page; import org.nutz.dao.pager.Pager; /** * 类描述: * 创建人:Wizzer * 联系方式:www.wizzer.cn * 创建时间:2013-12-12 下午3:16:21 * @version */ public class ExplicitPager extends Pager { private static final long serialVersionUID = 1L; int offset; int count; public ExplicitPager(int offset, int count) { super(); this.offset = offset; this.count = count; } public void setOffset( int offset) { this.offset = offset; } public void setSelectCount( int count) { this.count = count; } @Override public int getPageSize() { return count; } @Override public int getOffset() { return offset; } }
分页源代码(可参考上一篇文章,实现自定义SQL多表分页区间取值):
/** * 根据查询条件分页,返回封装好的QueryResult对象 * * @param dao * @param obj * @param cnd * @param pager * @return */ public <T> QueryResult listPage(Dao dao, Class<T> obj, Condition cnd,Pager pager) { List<T> list = dao.query(obj, cnd, pager); pager.setRecordCount(dao.count(obj, cnd));// 记录数需手动设置 return new QueryResult(list, pager); } /** * 根据自定义SQL分页,返回封装好的QueryResult对象 * @param dao * @param sql * @param pager * @return */ publicQueryResult listPageSql(Dao dao, Sql sql, Pager pager) { if(pager==null) return null; pager.setRecordCount(Daos.queryCount(dao, sql.getSourceSql()));// 记录数需手动设置 sql.setPager(pager); sql.setCallback(Sqls.callback.records()); dao.execute(sql); return new QueryResult(sql.getList(Map.class), pager); }
easyui.datagrid 调用示例:
@At @Ok("raw") public String test(@Param("page") int curPage, @Param("rows") int pageSize){ Sql sql=Sqls.create("select a.name,b.loginname From sys_unit a,sys_user b where a.id=b.unitid"); return daoCtl.listPageJsonSql(dao, sql, curPage, pageSize); }
源代码:
/** * 根据自定义SQL分页,返回封装好的QueryResult对象 * @param dao * @param sql * @param curPage * @param pageSize * @return */ public QueryResult listPageSql(Dao dao, Sql sql, int curPage,int pageSize) { Pager pager = dao.createPager(curPage, pageSize); pager.setRecordCount(Daos.queryCount(dao, sql.getSourceSql()));// 记录数需手动设置 sql.setPager(pager); sql.setCallback(Sqls.callback.records()); dao.execute(sql); return new QueryResult(sql.getList(Map.class), pager); } /** * 根据自定义SQL分页,返回封装好的 Easyui.datagrid JSON * @param dao * @param sql * @param curPage * @param pageSize * @return */ public String listPageJsonSql(Dao dao, Sql sql, int curPage, int pageSize) { Pager pager = dao.createPager(curPage, pageSize); pager.setRecordCount(Daos.queryCount(dao, sql.toString()));// 记录数需手动设置 sql.setPager(pager); sql.setCallback(Sqls.callback.records()); dao.execute(sql); Map<String, Object> jsonobj = new HashMap<String, Object>(); jsonobj.put("total", pager.getRecordCount()); jsonobj.put("rows", sql.getList(Map.class)); return Json.toJson(jsonobj); }
计划基于Nutz开发一套全新的系统管理-权限框架。界面演示:/?p=2586
之前尝试Nutz做过一个demo,但有很多不足,比如没有做防SQL注入、不能兼容多数据库、列表没有实现Ajax等。
基于多年来开发过程接触的各式各样的框架,想整一套开发方便UI简洁的框架,要求有如下功能:
1.开发简便、框架结构清晰
2.性能优越、兼容主流数据库、兼容各版本浏览器
3.UI简洁、开发简单、主流JS插件集成、全Ajax
4.集成权限框架、单点登陆、登陆日志、操作日志
5.工作流引擎、表单自定义
6.代码生成器
JS引入:
jquery-1.10.2.min.js
ueditor-1.2.6.1
My97Datepicker-4.8 Beta4
zDialog-2.0
ztree-3.5.15
框架引入:
Nutz-1.49.r2
log4j-1.2.17
json-1.0
c3p0-0.9.1.2
classes12
mysql-connector-java-5.1.14
ojdbc14
slf4j-api-1.6.1
slf4j-log4j12-1.6.1
sqljdbc4
velocity-1.7
velocity-tools-2.0
velocity-tools-generic-2.0
velocity-tools-view-2.0
json-lib-2.4-jdk15
ezmorph-1.0.6
commons-beanutils-1.7.0
commons-chain-1.1
commons-collections-3.2
commons-digester-1.8
commons-lang-2.2
commons-logging-1.1
commons-validator-1.3.1
待更新。
——————————————————–
在Eclipse中处理图片,需要引入两个包:
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
报错:
Access restriction: The type JPEGImageEncoder is not accessible due to restriction on required library C:\Java\jre1.6.0_07\lib\rt.jar
此时解决办法:
Eclipse默认把这些受访问限制的API设成了ERROR。只要把Windows-Preferences-Java-Complicer-Errors/Warnings里面的Deprecated and restricted API中的Forbidden references(access rules)选为Warning就可以编译通过。
—————————————————————
Nutz开发说明:
您一定了解SSH,还有Strust1+Velocity框架。前者配置文件实在是太多,多层架构,开发一个功能模块涉及很多类、很多关系,一方面上手非常难另外开发效率也不高。后者随着技术发展,对前端技术支持不太友好,比如Ajax、JS框架等,需要中间跳转页面进行输出,再者也需要配置。
根据项目开发中实际遇到的各种问题,研究各种框架优缺点之后,决定选择Nutz国产开源框架作为基础来构建本开发框架。选择Nutz有如下优点:
1、URL“零”配置
您再也不用为繁琐的配置文件烦恼了,Nutz-Mvc使用全注解的方式来封装URL,使用简单的@At 在类或方法上实现URL映射。
2、灵活的多数据源支持
通过Nutz.Ioc您只要在数据库配置文件datasource.json中配置对象,如dao1、dao2,那么在Action类中,
@Inject
protected Dao dao1;
@Inject
protected Dao dao2;
即可使用daoCtl的方法直接操作这两个数据库。daoCtl里集成了常用的添加、修改、查询、分页等方法。
3、功能强大的视图
Nutz-Mvc使用@Ok @Fail 注解,来实现执行成功或失败后的输出,可以外部重定向、内部重定向、返回字符串、返回JSON等等,总之您想要什么结果就能给您返回什么,再也不用使用单独的Action类来封装JSON数据了,您想要随时都可以有。
4、功能强大的数据库操作
Nutz.Dao封装了常用的数据库操作,减少自定义SQL的编写,提高开发效率和安全性,通过工具类
Cnd.where(“loginname”, “=”,
user.getLoginname())
和
Sql sql = Sqls.create("DELETE FROM $table WHERE name=@name");
sql.vars().set(“table”,”t_abc”);
sql.params().set(“name”,”Peter”);
中的sql.params().set() 方法,可以有效的防止SQL注入。
更多使用方法请参考本目录下的nutz开发手册。
5、Velocity
对于大家常用的Velocity模板语言,本框架也进行了整合。
req.setAttribute(“menulist”,
menulist);
将对象输出到页面,在页面上即可直接使用模板语言输出。
在UI表现层上面,本次框架也进行了调整,原则上支持纯Ajax开发。后台集成jQuery 、EasyUI、zDialog、EasyUI.DataGrid、ztree、My97DatePicker等JS组件。
1、兼容主流浏览器
您再也不用考虑浏览器兼容性问题,只要您的JS全部使用jQuery来编写,那么还用担心什么呢,本框架目前在IE6/IE7/IE8/IE9/IE10/IE11、Chrome、FrieFox、360测试通过。
2、zDialog的使用更加简便
通过使用Ajax以及对zDialog的优化,您再也不需要写中间页面进行页面跳转了。您可以在弹出框的父页面进行弹出表单的提交,在父页面写弹出框按钮的事件。
通过$DW.$(“#form1”).serialize(); 获取弹出窗口表单内容
$DW.$Z(“name”).focus(); 操作弹出框内容,赋值等
$D.close(); 关闭弹出窗口
让我们看一个完成的示例:
function addSave(){
if($DW.Verify.hasError()){
return;
}
jQuery.ajax({
type:
‘POST’,
url :
“/private/sys/unit/addSave” ,
data :
$DW.$(“#form1”).serialize(),
success :
function (res) {
if(res!=””){
$D.close();
Dialog.alert(“添加成功!“);
initTree();
view(res);
}else{
Dialog.alert(“添加失败!“);
}
return
false;
},
fail :
function(res) {
Dialog.alert(“系统错误?!”);
}
});
}
3、Ajax分页支持
集成jQuery.Easyui.DataGrid,从后台封装的JSON数据格式,到前台展示,分页查询从来没有如此简便过。
daoCtl.getListPageJson()
封装了easyui.datagrid分页所需格式数据,更不用您手动写烦人的JSON了。
4、权限控制到按钮
您只需要在JS中使用
Page.initBtn();
$!initBtn
即可控制页面按钮是否可用,当然,在此之前,您需要在资源菜单管理中添加按钮ID,在角色管理中对用户分配按钮权限。