Skip to content

Commit

Permalink
add: 开发指南基本写好了
Browse files Browse the repository at this point in the history
  • Loading branch information
Wizzercn committed Feb 22, 2017
1 parent 01704bb commit 9431b22
Show file tree
Hide file tree
Showing 23 changed files with 484 additions and 5 deletions.
2 changes: 1 addition & 1 deletion wk-wiki/01.QuickStart/01.02.DBConfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ var ioc = {

#### web-app\wk-web\src\main\resources\config\custom\db.properties

* java -cp druid-1.0.27.jar com.alibaba.druid.filter.config.ConfigTools root
* java -cp druid-1.0.28.jar com.alibaba.druid.filter.config.ConfigTools root
* 通过如上命令给root加密获取加密密码及公钥
* db.url 增加 &useSSL=false
* db.password 改为加密后的密码
Expand Down
15 changes: 14 additions & 1 deletion wk-wiki/02.Shiro/02.01.Desgin.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,17 @@
* Sys_user 用户表:用户属于一个单位;
* Sys_role 角色表:角色关联用户并关联菜单(权限),扩展Shiro的实现类,实现了用户登录 -> 查找角色 -> 查找菜单(权限),并加载到Shiro的Session中;
角色可以创建在单位下,设计初衷是为了单位权限的向下继承(适用于政务类项目);
* Sys_menu 菜单表:type=menu为菜单,type=data为权限,将Shiro的权限标识符和菜单有机结合起来,便于理解和功能实现;
* Sys_menu 菜单表:`type=menu`为菜单,`type=data`为权限,将Shiro的权限标识符和菜单有机结合起来,便于理解和功能实现;

# 如何新建菜单和分配权限

系统 -> 系统管理 -> 菜单管理

* 新建菜单,`target=data-pjax` 设置菜单的加载模式为pjax局部加载模式`后台右上角可切换加载模式`
* 新建权限,权限的上级必须是菜单,且设置权限标识,权限标识用于控制器类方法的 `@RequiresPermissions("sys.manager.conf.add")`
* `@RequiresAuthentication``@RequiresPermissions` 等shiro的注解使用说明,请自行补课;

系统 -> 系统管理 -> 角色管理

* 分配菜单权限 And 分配用户
* 分配权限后,必须重新登录才能加载新配的菜单和权限
6 changes: 3 additions & 3 deletions wk-wiki/02.Shiro/02.02.Settings.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

# Shiro如何起作用

* Shiro 拦截器:web.xml 配置ShiroFilter拦截器
* Shiro 配置文件:shiro.ini 配置了shiro缓存、cookie等之外,还配置了登录路径及作用范围(url),以及url对应的验证拦截器(如管理平台的PlatformAuthenticationFilter,将表单对象转为 shiro的token对象,captchaParam设置验证码表单参数名)和权限实现类(如管理平台的 PlatformAuthorizingRealm,验证token登录是否成功以及加载对应的菜单和权限)
* Shiro 过滤器:通过nutzwk-mvc-chain.json 配置整个MVC框架URL请求的动作链,其中 NutShiroProcessor 拦截shiro的注解方法并根据shiro抛出的异常,判断是否登录/是否具有权限,并根据是否Ajax请求,返回对应的错误消息;
* Shiro 拦截器:`web.xml` 配置ShiroFilter拦截器
* Shiro 配置文件:`shiro.ini` 配置了shiro缓存、cookie等之外,还配置了登录路径及作用范围(url),以及url对应的验证拦截器(如管理平台的PlatformAuthenticationFilter,将表单对象转为 shiro的token对象,captchaParam设置验证码表单参数名)和权限实现类(如管理平台的 PlatformAuthorizingRealm,验证token登录是否成功以及加载对应的菜单和权限)
* Shiro 过滤器:通过`nutzwk-mvc-chain.json` 配置整个MVC框架URL请求的动作链,其中 NutShiroProcessor 拦截shiro的注解方法并根据shiro抛出的异常,判断是否登录/是否具有权限,并根据是否Ajax请求,返回对应的错误消息;
* 注:以上配置文件请在项目中自行查找位置~~

# Shiro如何启用二级缓存`And集群部署`
Expand Down
36 changes: 36 additions & 0 deletions wk-wiki/03.Run/03.01.Init.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 入口类Module

`cn.wizzer.app.web.commons.core.Module`

~~~
@Modules(scanPackage = true, packages = "cn.wizzer")
@Ok("json:full")
@Fail("http:500")
@IocBy(type = ComboIocProvider.class, args = {"*json", "config/ioc/", "*anno", "cn.wizzer", "*tx", "*quartz", "*async"})
@Localization(value = "locales/", defaultLocalizationKey = "zh_CN")
@Encoding(input = "UTF-8", output = "UTF-8")
@Views({BeetlViewMaker.class, PdfViewMaker.class})
@SetupBy(value = Setup.class)
@ChainBy(args = "config/chain/nutzwk-mvc-chain.json")
@SessionBy(ShiroSessionProvider.class)
public class Module {
}
~~~
* `@Modules` 扫描package及子package下的类中的入口方法
* `@Ok` 全局配置响应成功返回的结果,这里配的是输出json格式(含null值)
* `@Fail` 全局配置响应失败返回的结果,这里配的是返回500页面
* `@IocBy` 配置加载容器对象,详见 [http://nutzam.com/core/ioc/loader_annotation.html](http://nutzam.com/core/ioc/loader_annotation.html)
* `@Localization` 语言包放置的路径,及默认的语言
* `@Encoding` 运行时、输入输出、数据库、文件编码等,统一为UTF-8
* `@Views` 加载视图,这里添加了beetl模板引擎及pdf视图,@Ok("beetl:/..html")返回页面,@Ok("raw:pdf") 结果直接生成PDF文件,@Ok("raw:png") 直接输出图片等等,详见 [http://nutzam.com/core/mvc/view.html](http://nutzam.com/core/mvc/view.html)
* `@SessionBy` session交给shiro管理
* `@ChainBy` 加载的动作链配置文件
* `@SetupBy` 启动类,初始化数据

# 启动类Setup

`cn.wizzer.app.web.commons.core.Setup`

* 为Globals全局变量赋值,如Globals.AppRoot项目物理路径,
* 启动时根据Sys_user表是否有数据,初始化表数据
* 初始化自定义路由、定时任务、插件等,详见代码
34 changes: 34 additions & 0 deletions wk-wiki/03.Run/03.02.01.GlobalsSettingProcessor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# GlobalsSettingProcessor
`cn.wizzer.app.web.commons.processor.GlobalsSettingProcessor`

~~~
public class GlobalsSettingProcessor extends AbstractProcessor {
@SuppressWarnings("rawtypes")
public void process(ActionContext ac) throws Throwable {
ac.getRequest().setAttribute("AppRoot", Globals.AppRoot);
ac.getRequest().setAttribute("AppBase", Globals.AppBase);
ac.getRequest().setAttribute("AppName", Globals.AppName);
ac.getRequest().setAttribute("AppDomain", Globals.AppDomain);
ac.getRequest().setAttribute("AppShrotName", Globals.AppShrotName);
ac.getRequest().setAttribute("shiro", Mvcs.ctx().getDefaultIoc().get(ShiroUtil.class));
ac.getRequest().setAttribute("date", Mvcs.ctx().getDefaultIoc().get(DateUtil.class));
ac.getRequest().setAttribute("string", Mvcs.ctx().getDefaultIoc().get(StringUtil.class));
// 如果url中有语言属性则设置
String lang=ac.getRequest().getParameter("lang");
if (!Strings.isEmpty(lang)) {
Mvcs.setLocalizationKey(lang);
}else{
// Mvcs.getLocalizationKey() 1.r.56 版本是null,所以要做两次判断, 1.r.57已修复为默认值 Nutz:Fix issue 1072
lang=Strings.isBlank(Mvcs.getLocalizationKey())?Mvcs.getDefaultLocalizationKey():Mvcs.getLocalizationKey();
}
ac.getRequest().setAttribute("lang", lang);
doNext(ac);
}
}
~~~

* 页面上可以通过beetl或其他模板引擎,获取 ${AppBase} 部署路径(或者使用系统自带的 ${base} 对象)
* 页面上可通过 `@shiro.hasRole("sysadmin")``@shiro.hasPermission("sys.manager.user.add")` 等验证是否有对应的权限,`${@shiro.getPrincipalProperty('username')}`获取当前登录用户的一个属性,详见 `ShiroUtil`
* 设置了根据lang 参数值加载不同的国际化语言,前台使用 `${msg['index.custommenu']}` 输出对应的字符串,后台使用 `Mvcs.getMessage(req, msg)` 获取字符串
24 changes: 24 additions & 0 deletions wk-wiki/03.Run/03.02.02.LogTimeProcessor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# LogTimeProcessor

`cn.wizzer.app.web.commons.processor.LogTimeProcessor`

~~~
public class LogTimeProcessor extends AbstractProcessor {
private static final Log log = Logs.get();
public void process(ActionContext ac) throws Throwable {
Stopwatch sw = Stopwatch.begin();
try {
doNext(ac);
} finally {
sw.stop();
if (log.isDebugEnabled()) {
HttpServletRequest req = ac.getRequest();
log.debugf("[%-4s]URI=%s %sms", req.getMethod(), req.getRequestURI(), sw.getDuration());
}
}
}
}
~~~
* `log.isDebugEnabled()` log4j里配为debug模式才会输出响应时间
22 changes: 22 additions & 0 deletions wk-wiki/03.Run/03.02.03.XssSqlFilterProcessor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# XssSqlFilterProcessor

`cn.wizzer.app.web.commons.processor.XssSqlFilterProcessor`

~~~
public void process(ActionContext ac) throws Throwable {
if (checkParams(ac)) {
if (NutShiro.isAjax(ac.getRequest())) {
ac.getResponse().addHeader("loginStatus", "paramsDenied");
NutShiro.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error(Mvcs.getMessage(ac.getRequest(), "system.paramserror")));
} else {
new ForwardView(lerrorUri).render(ac.getRequest(), ac.getResponse(), Mvcs.getMessage(ac.getRequest(), "system.paramserror"));
}
return;
}
doNext(ac);
}
...
~~~

* 判断是否Ajax请求,若包含关键词则输出错误信息,或跳转到错误页
* 表单字段中包含English 的时候很容易被拦截,请根据业务适当修改关键词
28 changes: 28 additions & 0 deletions wk-wiki/03.Run/03.02.Processor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 动作链

`resources/config/chain/nutzwk-mvc-chain.json`

~~~
var chain={
"default" : {
"ps" : [
"cn.wizzer.app.web.commons.processor.LogTimeProcessor",
"cn.wizzer.app.web.commons.processor.GlobalsSettingProcessor",
"org.nutz.mvc.impl.processor.UpdateRequestAttributesProcessor",
"org.nutz.mvc.impl.processor.EncodingProcessor",
"org.nutz.mvc.impl.processor.ModuleProcessor",
"cn.wizzer.app.web.commons.processor.NutShiroProcessor",
"cn.wizzer.app.web.commons.processor.XssSqlFilterProcessor",
"org.nutz.mvc.impl.processor.ActionFiltersProcessor",
"org.nutz.mvc.impl.processor.AdaptorProcessor",
"org.nutz.mvc.impl.processor.MethodInvokeProcessor",
"org.nutz.mvc.impl.processor.ViewProcessor"
],
"error" : 'org.nutz.mvc.impl.processor.FailProcessor'
}
};
~~~
* [GlobalsSettingProcessor](03.02.01.GlobalsSettingProcessor.md) 将全局变量、常用工具类、国际化语言对象输出到视图
* [LogTimeProcessor](03.02.02.LogTimeProcessor.md) 打印请求的响应耗时
* [XssSqlFilterProcessor](03.02.03.XssSqlFilterProcessor.md) 过滤SQL注入和跨站攻击关键词
* [NutShiroProcessor](../02.Shiro/02.02.Settings.md) Shiro权限拦截,在`权限体系`里有过说明
28 changes: 28 additions & 0 deletions wk-wiki/03.Run/03.03.01.Filters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# @Filters

注解 '@Filters' 的值是一个 '@By' 注解的数组,它可以声明在这三个地方

入口函数
子模块
主模块
其中入口函数的 @Filters 优先级更高,其次是子模块,最后是主模块。

就是说,你在入口模块声明了两个过滤器:

~~~
@Filters({@By(type=Filter1.class), @By(type=Filter2.class)})
public class DemoController{
...
~~~

不使用任何过滤器:

~~~
...
@At
@Filters
public String myFunction(){
...
~~~

* 更多介绍,详见 [http://nutzam.com/core/mvc/action_filter.html](http://nutzam.com/core/mvc/action_filter.html)
46 changes: 46 additions & 0 deletions wk-wiki/03.Run/03.03.02.RouteFilter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# RouteFilter 自定义路由

## web.xml

~~~
<filter>
<filter-name>route</filter-name>
<filter-class>cn.wizzer.app.web.commons.filter.RouteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>route</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
~~~

## RouteFilter
~~~
public class RouteFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req2 = (HttpServletRequest) req;
HttpServletResponse res2 = (HttpServletResponse) res;
res2.setCharacterEncoding("utf-8");
req2.setCharacterEncoding("utf-8");
Sys_route route = Globals.RouteMap.get(req2.getRequestURI().replace(Globals.AppBase, ""));
if (route != null) {
if ("show".equals(route.getType())) {
res2.sendRedirect(route.getToUrl());
} else {
req2.getRequestDispatcher(route.getToUrl()).forward(req2, res2);
}
} else chain.doFilter(req2, res2);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
@Override
public void destroy() {
}
}
~~~
4 changes: 4 additions & 0 deletions wk-wiki/03.Run/03.03.Filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# 拦截器

* [通过 @Filters 注解实现](03.03.01.Filters.md)
* [通过 web.xml 配置实现(如自定义路由)](03.03.02.RouteFilter.md)
12 changes: 12 additions & 0 deletions wk-wiki/03.Run/03.04.Async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# @Async 注解

在public实例方法标注@Async, 前提是Ioc容器内的对象,例如标注了@IocBean或js/xml中声明了该对象

~~~
@Async
public void sendEmail(....) {
}
~~~
通常来说,异步执行方法的返回值是没有意义的,为避免歧义,请尽量使用void作为返回值类型

详见 [http://nutzam.com/core/aop/aop_async.html](http://nutzam.com/core/aop/aop_async.html)
11 changes: 11 additions & 0 deletions wk-wiki/03.Run/03.05.Slog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# @SLog 注解

~~~
@SLog(tag = "修改参数", msg = "${args[0].configKey}:req.getAttribute('name')")
public Object editDo(@Param("..") Sys_config conf) {
...
~~~

* 操作日志记录在 sys_log 表中,建议做个清空日志表的定时任务
* 可根据业务需要,将 sys_log 扩展成按月动态分表
* 具体实现可见源码 cn.wizzer.app.web.commons.slog 包
26 changes: 26 additions & 0 deletions wk-wiki/04.Code/04.01.Pojo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Pojo实体类

~~~
@Table("wx_config")
public class Wx_config extends BaseModel implements Serializable {
private static final long serialVersionUID = 1L;
@Column
@Name
@ColDefine(type = ColType.VARCHAR, width = 32)
@Prev(els = {@EL("uuid()")})
private String id;
@Column
@Comment("发送状态")
@ColDefine(type = ColType.INT)
protected Integer status;
~~~
* Pojo类继承BaseModel类,启动时会自动建表同时包含BaseModel中的三个字段
* 建议所有的ID主键,使用UUID字符串
* `@ColDefine(type = ColType.INT)` int类型字段不要设置width,因为这里的width不是数据库里的长度,且java类型设置为Integer,因为后台表单提交都是调用dao.updateIgnoreNull() 方法,如果是int且表单没有隐藏的input则会被置为0
* 加了 `@Comment` 注解的字段才会被代码生成器识别,生成到模板文件和国际化语言中
* 命名规范: Wx_config,wx为模块名称,代码生成器会根据下划线切分数组,组装路径
* 注意:请遵从系统自带模块的包及类命名规范

详见 [http://nutzam.com/core/dao/entity.html](http://nutzam.com/core/dao/entity.html)
7 changes: 7 additions & 0 deletions wk-wiki/04.Code/04.02.Generator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 代码生成器
* IDEA Settings --> Plugins --> Install plugin from disk --> wk-code-ideaplugin.jar
* 创建实体类,必须有@Table,若字段需生成到模板页面则需加 @Comment 字段备注
* 编译wk-web项目,使其打包发布至 target/ 目录
* 在实体类上鼠标右击,Code(Alt+Insert/Mouse Right) --> Generate --> wk mvc

![IDEA插件截图](../images/09.png)
27 changes: 27 additions & 0 deletions wk-wiki/04.Code/04.03.Service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Service类

必须写一个接口类:

~~~
public interface CmsArticleService extends BaseService<Cms_article>{
}
~~~

以及实现类,并注入dao:

~~~
@IocBean(args = {"refer:dao"})
public class CmsArticleServiceImpl extends BaseServiceImpl<Cms_article> implements CmsArticleService {
public CmsArticleServiceImpl(Dao dao) {
super(dao);
}
}
~~~

* Service写成接口,这样才能映射服务接口,配合dubbo、rsf等实现分布式部署
* 注入dao或者dao2等,可方便多数据源业务
* 建议所有的业务都写在 Service 中
* 配合 `@Async` 可以实现异步方法调用
* 使用 `@Aop(TransAop.READ_COMMITTED)` 实现事务,使用事务时不可捕获异常,否则事务不能回滚
* 注意:请遵从系统自带模块的包及类命名规范
Loading

0 comments on commit 9431b22

Please sign in to comment.