请上传宽度大于 1200px,高度大于 164px 的封面图片
    调整图片尺寸与位置
    滚轮可以放大缩小图片尺寸,按住图片拖动可调整位置,多余的会自动被裁剪掉
取消
插件开发者(uid:539676)
插件开发者 职业资格认证:FCP-FineReport插件开发工程师 | FCA-FineReport
动态数据源插件3.0完成
    使用场景:     当你有多个数据库时,表结构是一样的,只是数据不一样,你对于某一张报表,你希望员工A能看到数据库A里的数据,员工B看到数据库B里的数据。或者员工A填报的数据进入数据库A,员工B填报的数据进行数据库B。   使用演示:    第一步:制作报表     有两个数据连接finedb,myfinedb分别对应两个不同的数据库,这两个数据库都有表 emo_product(产品表)与demo_product_category(产品分类表)     新建一张普通报表,一个数据集,两张表联合查询数据,sql语句为: select * from demo_product a,demo_product_category b where a.category_id = b.id       报表里放一个表格组件放上对应的字段。   第二步:配置报表。   插件实现原理是再URL上添加一个参数role,配置的时候可以这样配,role=roleA的时候报表访问的数据是数据连接finedb的数据,role=roleB的时候报表访问的数据是数据连接myfinedb的数据。 再决策系统中配置:   第三步:访问报表 先使用role=roleA访问: 然后使用role=roleB来访问;     最后:本插件支持普通报表,决策报表,填报报表。报表中的组件,另外fineBI也实现了动态切换连接。        
这样的填报界面你喜欢吗
自带的看腻了。用自己开发的组件实现的填报功能。  
报表中同一个sql语句,根据不同的用户使用不同的连接
​  1 先写配置文件,在报表服务器中的图中目录下新建conf.xml: ​编辑 xml的内容:   <root> <connect reportName="WorkBook2.cpt" userName="zhangsan" dbConnectName="mysql"/> <connect reportName="WorkBook2.cpt" userName="lisi" dbConnectName="myfindb"/> <connect reportName="WorkBook4.cpt" userName="zhangsan" dbConnectName="mysql1"/> <connect reportName="WorkBook4.cpt" userName="lisi" dbConnectName="myfindb"/> </root>         reportName:报表路径         userName:用户名         dbConnectName:该用户使用的连接。 2 安装插件。 3 访问报表,url上需要加上currentUserName参数,currentUserName就是传递的用户名。 4 效果: ​编辑   ​编辑 不同的用户显示了不同的数据。   ​编辑 这里也能看出。 最后,插件下载地址:https://download.csdn.net/download/sixingbugai/86035828 ​
官方数据工厂插件太贵,来看看我做的。
​  帆软官方的数据工厂插件地址:帆软市场,太贵了。自己也开发一个,先看下界面: ​编辑  自己开发的现在支持HTTP,HTTPS的GET,POST请求,支持URL从配置文件读取,支持URL加密请求结果支持json,xml,支持对请求结果的扩展处理,支持对HTTP请求的BODY,HEADER参数扩展处理。   1 GET请求:         后台写法:                  @CrossOrigin(origins = "*") @RequestMapping(value = "/finereport", method = RequestMethod.GET) @ResponseBody public HttpReqResult finereport(HttpServletRequest req, HttpServletResponse res, @RequestParam("name") String name, @RequestParam("age") int age) { System.out.println("finereport"); ArrayList<HashMap<String, Object>> result = new ArrayList<>(); HashMap<String, Object> param = new HashMap<>(); result.add(param); param.put("name", "aaass"); param.put("age", 1234); param = new HashMap<>(); result.add(param); param.put("name", "aas定时 方法"); param.put("age", 2356); param = new HashMap<>(); result.add(param); param.put("name", "322试试"); param.put("age", 111); HashMap<String, Object> param1 = new HashMap<>(); param1.put("aaa", result); return HttpReqResult.ok(param1); } 返回结果为:          {"status":"ok","data":,"errorMsg":null} 界面配置: ​编辑  点击预览可以看到返回的结果: ​编辑   2 POST请求:         参数传递按json格式         后台写法:          @CrossOrigin(origins = "*") @RequestMapping(value = "/finereport2", method = RequestMethod.POST) @ResponseBody public HttpReqResult finereport2(HttpServletRequest req, HttpServletResponse res, @RequestBody FineReportBean fineReportBean) { System.out.println("finereport2"); String token = req.getHeader("token"); System.out.println("token:"+token); ArrayList<HashMap<String, Object>> result = new ArrayList<>(); HashMap<String, Object> param = new HashMap<>(); result.add(param); param.put("name", "张三"); param.put("age", 34); param = new HashMap<>(); result.add(param); param.put("name", "李四"); param.put("age", 112); param = new HashMap<>(); result.add(param); param.put("name", "王五"); param.put("age", 1232); return HttpReqResult.ok(result); }       设计器配置:         ​编辑 预览结果:         ​编辑           参数传递按 application/x-www-form-urlencoded         后台代码:          @CrossOrigin(origins = "*") @RequestMapping(value = "/finereport3", method = RequestMethod.POST) @ResponseBody public HttpReqResult finereport3(HttpServletRequest req, HttpServletResponse res, @RequestParam("name") String name, @RequestParam("age") int age) { System.out.println("finereport3:name="+name+",age="+age); String token = req.getHeader("token"); System.out.println("token:"+token); ArrayList<HashMap<String, Object>> result = new ArrayList<>(); HashMap<String, Object> param = new HashMap<>(); result.add(param); param.put("name", "张三1"); param.put("age", 314); param = new HashMap<>(); result.add(param); param.put("name", "李四1"); param.put("age", 1121); param = new HashMap<>(); result.add(param); param.put("name", "王五1"); param.put("age", 12321); param = new HashMap<>(); result.add(param); param.put("name", "王五2"); param.put("age", 12321); param = new HashMap<>(); result.add(param); param.put("name", "王五3"); param.put("age", 12321); param = new HashMap<>(); result.add(param); param.put("name", "王五4"); param.put("age", 12321); param = new HashMap<>(); result.add(param); param.put("name", "王五5"); param.put("age", 12321); return HttpReqResult.ok(result); } 设计器配置:         ​编辑 预览结果: ​编辑  参数传递按  multipart/form-data        后台代码:          //post 接收form-data参数 @CrossOrigin(origins = "*") @RequestMapping(value = "/finereport4", method = RequestMethod.POST) @ResponseBody public HttpReqResult finereport4(HttpServletRequest req, HttpServletResponse res, @RequestParam("name") String name, @RequestParam("age") int age ) throws IOException { System.out.println("finereport4:name="+name+",age="+age); String token = req.getHeader("token"); System.out.println("token:"+token); ArrayList<HashMap<String, Object>> result = new ArrayList<>(); HashMap<String, Object> param = new HashMap<>(); result.add(param); param.put("name", "张三4"); param.put("age", 3144); param = new HashMap<>(); result.add(param); param.put("name", "李四4"); param.put("age", 11214); param = new HashMap<>(); result.add(param); param.put("name", "王五4"); param.put("age", 123214); return HttpReqResult.ok(result); } 设计器配置: ​编辑 预览结果: ​编辑   3 URL配置到文件中,并且URL可加密         使用场景是:                 1 我们将URL全部写到配置文件中,设计器中只需要从下拉框选择。                 2 如果我们有多套环境,每个环境的对应的URL不一致,但是我们再设计器中配好以后如果环境换了还需要去改模板里的URL。这个问题本插件也会解决。                 3 如果不想别人看到我们的URL地址,本插件可以将URL加密,等真正运行时再去解密。         使用说明:                 配置文件放到报表服务器的\webapps\webroot\dataset下,配置文件名字为:conf.xml                 ​编辑  看下conf.xml的内容:         ​ <root> <httpUrls> <httpUrl name="get请求" url="http://127.0.0.1:8082/finereport" publicKey=""></httpUrl> <httpUrl name="post请求json" url="4c9USpp2zUsTI+0m0/rbw0NY52bJLxWQrYjcRsAyfo6mXQ5ZEYBb6E40mOZwn+5s" publicKey="ea26dtw34422**aa"></httpUrl> <httpUrl name="post请求urlencoded" url="4c9USpp2zUsTI+0m0/rbw0NY52bJLxWQrYjcRsAyfo6X7f8iUoyP3E3iMD1KypmL" publicKey="ea26dtw34422**aa"></httpUrl> <httpUrl name="post请求form-data" url="http://127.0.0.1:8082/finereport4" publicKey=""></httpUrl> </httpUrls> </root> ​ 可以看到每个URL有三个属性:         name:url名字,显示再设计器中的下拉框中,模板中保存的也是这个名字。         url:实际的请求URL,可加密,         publicKey:解密密钥. 这些会出现再设计器中的下拉框中: ​编辑  这样设计人员看不到具体的url,模板中也只保存了name属性,并且模板中保存的内容都是加密的。这样就把url地址保护起来了。 那URL如果加密呢? 再我提供的插件包中有一个plugin-data-factory-lib.jar,里面实现了一个加密解密的类。比如加密: public static void main(String args) { // TODO Auto-generated method stub String publicKey = "ea26dtw34422**aa"; String src = "http://127.0.0.1:8082/finereport3"; try { String dsc = SecretManager.Encrypt(src, publicKey); System.out.println("dsc:"+dsc); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } 这样我们就可以得到加密后的url。   最后:试用插件下载地址:https://download.csdn.net/download/sixingbugai/85965205?spm=1001.2014.3001.5503,有需要正式版的可以加WX:aiyowa1021 ​
能看懂这两张图的是高手
默认预览报表加载的finereport.js: 下面是安装了我的插件之后预览报表加载的finereport.js:   能看出这两张图区别的是高手!!        
finereport前端组件类思维导图
总结finereport前端组件类思维导图
简单分析帆软报表中一次HTTP请求的过程。
​  我们知道帆软Report本质是一个web项目,所以他也有filter,servelt。当一个请求来到时,先经过filter,然后再经过servlet。   第一步,要经过的filter: 帆软报表内部加了7个filter。他们分别是:         ApplicationFilterConfig         ApplicationFilterConfig         ApplicationFilterConfig         ApplicationFilterConfig         ApplicationFilterConfig         ApplicationFilterConfig         ApplicationFilterConfig 这些filter是在com.fr.decision.base.DecisionServletInitializer上添加的,DecisionServletInitializer其实就是一个Activator,看了前面的文章知道Activator是个什么东西。 在DecisionServletInitializer的start方法中:          public void start() { ServletContext servletContext = (ServletContext)this.getModule().upFindSingleton(ServletContext.class); if (servletContext != null) { AnnotationConfigWebApplicationContext context = (AnnotationConfigWebApplicationContext)this.getModule().upFindSingleton(AnnotationConfigWebApplicationContext.class); context.register(new Class{DecisionHandlerAdapter.class}); this.initUrlEncodeStyle(servletContext); this.listenGlobalServletFilter(servletContext); Dynamic servlet = servletContext.addServlet(ServerConfig.getInstance().getServletName(), new DispatcherServlet(context)); servlet.addMapping(new String{ServerConfig.getInstance().getServletMapping()}); servlet.setLoadOnStartup(1); servlet.setAsyncSupported(true); this.listenEmbedServletFilter(servletContext); this.addCounterFilter(servletContext); this.addPluginStoreFilter(servletContext); this.addTenantFilter(servletContext); SateVariableManager.put("fineContextPath", servletContext.getContextPath()); SateVariableManager.put("fineServletURL", servletContext.getContextPath() + "/" + ServerConfig.getInstance().getServletName()); context.register(new Class{DecisionConfiguration.class}); FineLoggerFactory.getLogger().info("Server started with build: {}", new Object{GeneralUtils.readFullBuildNO()}); } }         其中的: this.listenGlobalServletFilter(servletContext); this.listenEmbedServletFilter(servletContext); this.addCounterFilter(servletContext); this.addPluginStoreFilter(servletContext); this.addTenantFilter(servletContext); 都是跟filter相关的。 1 先来看listenGlobalServletFilter          private void listenGlobalServletFilter(ServletContext servletContext) { Set<GlobalRequestFilterProvider> set = ExtraDecisionClassManager.getInstance().getArray("GlobalRequestFilterProvider"); Set<GlobalRequestFilterProvider> sort = new TreeSet(set); Iterator var4 = sort.iterator(); while(var4.hasNext()) { final GlobalRequestFilterProvider provider = (GlobalRequestFilterProvider)var4.next(); String externalFilterClassName = provider.externalFilterClassName(); javax.servlet.FilterRegistration.Dynamic servletFilter; if (StringUtils.isBlank(externalFilterClassName)) { servletFilter = servletContext.addFilter(provider.filterName(), new javax.servlet.Filter() { public void init(FilterConfig filterConfig) throws ServletException { provider.init(filterConfig); } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { provider.doFilter((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse, filterChain); } public void destroy() { provider.destroy(); } }); } else { servletFilter = servletContext.addFilter(provider.filterName(), externalFilterClassName); } Map<String, String> parameters = provider.initializationParameters(); if (parameters != null) { servletFilter.setInitParameters(provider.initializationParameters()); } EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ERROR); servletFilter.addMappingForUrlPatterns(dispatcherTypes, false, provider.urlPatterns()); } }         可以看出先从我们的扩展GlobalRequestFilterProvider中取出所有插件的这种扩展。然后遍历每个扩展,如果扩展的externalFilterClassName为空,那么创建一个内部filter,在内部filter中再来调用我们provider中的init,doFilter,destory方法,说明我们的provider不是一个标准的filter类。如果externalFilterClassName不为空,意思就是我们可以提供一个标准的filter,在这里将filter全类名赋给externalFilterClassName,那么就会将我们的标准filter加进来。并且将provider提供的initParams传递到标准filter中。 2 看listenEmbedServletFilter:          private void listenEmbedServletFilter(ServletContext servletContext) { final Set<EmbedRequestFilterProvider> set = new LinkedHashSet(ExtraDecisionClassManager.getInstance().getArray("EmbedRequestFilterProvider")); Filter<PluginContext> filter = new Filter<PluginContext>() { public boolean accept(PluginContext context) { return context.contain(PluginModule.ExtraDecision, "EmbedRequestFilterProvider"); } }; EventDispatcher.listen(PluginEventType.AfterRun, new Listener<PluginContext>() { public void on(Event event, PluginContext context) { Set<EmbedRequestFilterProvider> providers = context.getRuntime().get(PluginModule.ExtraDecision, "EmbedRequestFilterProvider"); set.addAll(providers); } }, filter); EventDispatcher.listen(PluginEventType.BeforeStop, new Listener<PluginContext>() { public void on(Event event, PluginContext context) { Set<EmbedRequestFilterProvider> providers = context.getRuntime().get(PluginModule.ExtraDecision, "EmbedRequestFilterProvider"); set.removeAll(providers); } }, filter); javax.servlet.FilterRegistration.Dynamic servletFilter = servletContext.addFilter(ServerConfig.getInstance().getServletName(), new javax.servlet.Filter() { public void init(FilterConfig filterConfig) { Iterator var2 = set.iterator(); while(var2.hasNext()) { EmbedRequestFilterProvider one = (EmbedRequestFilterProvider)var2.next(); one.init(filterConfig); } } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { Iterator var4 = set.iterator(); while(var4.hasNext()) { EmbedRequestFilterProvider one = (EmbedRequestFilterProvider)var4.next(); one.filter((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse); } filterChain.doFilter(servletRequest, servletResponse); } public void destroy() { Iterator var1 = set.iterator(); while(var1.hasNext()) { EmbedRequestFilterProvider one = (EmbedRequestFilterProvider)var1.next(); one.destroy(); } } }); EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ERROR); servletFilter.addMappingForUrlPatterns(dispatcherTypes, false, new String{ServerConfig.getInstance().getServletMapping()}); }         可以看出这里创建一个内部filter,我们的扩展EmbedRequestFilterProvider是在内部filter调用的。 下一个 addCounterFilter:          private void addCounterFilter(ServletContext servletContext) { javax.servlet.FilterRegistration.Dynamic servletFilter = servletContext.addFilter("counter", new OncePerRequestFilter() { protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { String sessionID = WebUtils.getHTTPRequestParameter(httpServletRequest, "sessionID"); try { AccessContext.getRequest().inc(); if (sessionID != null) { AccessContext.getSessionRequest().inc(); } filterChain.doFilter(httpServletRequest, httpServletResponse); } finally { AccessContext.getRequest().dec(); if (sessionID != null) { AccessContext.getSessionRequest().dec(); } } } }); EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ERROR); servletFilter.addMappingForUrlPatterns(dispatcherTypes, false, new String{ServerConfig.getInstance().getServletMapping()}); } 可以看出这是一个统计的filter。   下一个addPluginStoreFilter:          private void addPluginStoreFilter(ServletContext servletContext) { javax.servlet.FilterRegistration.Dynamic servletFilter = servletContext.addFilter("plugin-store-filter", new PluginStoreFilter()); EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ERROR); servletFilter.addMappingForUrlPatterns(dispatcherTypes, false, new String{"/scripts/*", "/upm/*"}); }         这是一个扩展插件里的js资源的filter。   下一个addTenantFilter:          private void addTenantFilter(ServletContext servletContext) { javax.servlet.FilterRegistration.Dynamic tenantFilter = servletContext.addFilter(TenantFilter.class.getSimpleName(), TenantFilter.getInstance()); tenantFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ERROR), false, new String{"/*"}); }         这个暂时还没看出是干啥的。   当这些filter都通过后,下一步进入servlet。   第二部:经过servlet:         servelt注册也是在com.fr.decision.base.DecisionServletInitializer中注册的:                  Dynamic servlet = servletContext.addServlet(ServerConfig.getInstance().getServletName(), new DispatcherServlet(context)); servlet.addMapping(new String{ServerConfig.getInstance().getServletMapping()}); servlet.setLoadOnStartup(1); servlet.setAsyncSupported(true);         对,就是DispatcherServlet。         在DispatcherServlet的doDispatch方法中有几行代码分别对请求进行某些处理:          mappedHandler.applyPreHandle(processedRequest, response) //预先处理 mappedHandler.applyPostHandle(processedRequest, response, mv) //正式处理 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response)         只有 applyPreHandle成功后才会进行后面的。 下面介绍applyPreHandle,         applyPreHandle方法会调用HandlerExecutionChain的applyPreHandle,来看看它的整个方法 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor interceptors = this.getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) { HandlerInterceptor interceptor = interceptors; if (!interceptor.preHandle(request, response, this.handler)) { this.triggerAfterCompletion(request, response, (Exception)null); return false; } } } return true; }         可以看到这里会获取一些Interceptors来,最终都是调用Interceptors的preHandle。内部默认有​这12个Interceptor拦截器。 这里比较重要的interceptor有:         RequestPreHandleInterceptor:                 它里面有扩展RequestPreHandleProvider,看看它的preHandle:          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { Set<RequestPreHandleProvider> providers = ExtraDecisionClassManager.getInstance().getArray("RequestPreHandleProvider"); Iterator var5 = providers.iterator(); while(var5.hasNext()) { RequestPreHandleProvider provider = (RequestPreHandleProvider)var5.next(); if (provider.accept(request)) { return provider.preHandle(request, response); } } } catch (Exception var7) { FineLoggerFactory.getLogger().error(var7.getMessage(), var7); } return true; } ,可以看到这里会调用扩展RequestPreHandleProvider的 accept,preHandle处理请求。系统内部实现了两个provider,Html5URLRedirectProvider,Html5HeaderHandlerProvider.         DecisionInterceptor:                 它里面虽然没有扩展,但是可以通过 PreHandlerFactory.getInstance().registerRequestCheckers(RequestChecker checker)来注册RequestChecker来对请求进行出来,来看他的preHandle,          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { EventDispatcher.fire(RequestBeginEndEvent.REQUEST_BEGIN, new RequestBeginEndEventInfo(request, response)); HandlerMethod handlerMethod = (HandlerMethod)handler; RequestChecker checker = PreHandlerFactory.getInstance().getRequestChecker(request, handlerMethod); return checker.checkRequest(request, response, handlerMethod); }  系统内部加了:​这四个requestChecker。但是加入到里面的requestChecker,会执行他们的acceptRequest方法,如果返回false,那么就会调用它的checkRequest来处理,如果返回true是不会调用它的checkRequest方法的。   经过上面拦截器的运行,如果其中有哪个interceptor的preHandle返回false了,请求就不往后执行了。 如果返回true,就继续执行,当执行到mappedHandler.applyPostHandle(processedRequest, response, mv);时,又会调用那些intecertor的postHandle来处理请求。          void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception { HandlerInterceptor interceptors = this.getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for(int i = interceptors.length - 1; i >= 0; --i) { HandlerInterceptor interceptor = interceptors; interceptor.postHandle(request, response, this.handler, mv); } } }  大部分intercetor的postHandle方法都是空方法。,所以intercetor主要用来预处理请求的。 ​
解决Iframe嵌入帆软BI系统后,Chrome升级后跨域出现登录界面,Cookie写入不成功。
​  1 先看效果: ​   cookie写入不成功,是因为google chrome的高版本为了防止CSRF 攻击,默认将Cookie的SameSite设置为lax了,导致cookie跨域的时候就写不成功了。如果我们在嵌入页面的url上加上fine_auth_token参数值会怎样呢?看下图: ​   ​ 从上面看出当请求我们的BI页面时,Set-Cookie那里有个黄色的感叹号,表示cookie写入失败了,那一行小字说的是 SameSite=Lax了,所以跨域就写不了Cookie了。   为了解决这个问题,网上有一些解决方案,经过本人实际测试,开发了一个插件解决了这个问题,最终效果如下图: ​   当访问页面的时候,可以看到有两个Set-cookie了,一个有感叹号,一个没有,有感叹号的是BI系统内部写的cookie,是不成功的,没有感叹号是我写的,可以看到写入成功了。 经过本人实测,发现如果BI系统安装在Windows系统上,直接安装插件即可解决,如果是在linux系统中,需要将要访问的URL转成HTTPS才能成功。BI系统是在5.1.16版本,Chrome版本 94.0.4606.61 上测试的。 ​ 编辑于 2021-9-30 16:40
帆软报表邮箱验证码登录
安装插件之后,需要在\webroot\customerlogin下写配置文件email.xml,内容为: <root> <emailAccount>邮箱发送者的账号</emailAccount> <emailPassword>邮箱发送者密码</emailPassword> <emailSMTPHost>邮箱的smtp服务器地址</emailSMTPHost> </root>   当使用我前面文章说的自定义插件后跳转到自定义登录界面,如图: 当我们点击发送验证码,deug代码能看到产生的验证码: 这里能看到系统产生的验证码,会发送到刚才输入的邮箱,如下图: 我们在步骤2中输入验证码,点击登录,就会进入系统了。
帆软报表登录-某些用户需要用自定义的登陆界面,有些用户用默认登录界面
当我们做项目的时候,考虑到安全,可能存在这样的需求,对外客户需要用 自定义的登录(比如验证码,第三方token),对内用默认的登录界面就行了。本插件就可以实现该功能。fine-plugin-com.fr.plugin.wx.customer.login-1.0.zip (40.55 K) 使用说明: 装好插件之后,需要在webapps\webroot下创建文件夹customerlogin, 在customerlogin文件夹下创建配置文件conf.xml, conf.xml的内容为 : <root> <customerLoginRole>a</customerLoginRole> <customerLoginUrl>/webroot/decision/file?path=您的自定义登录界面的路径</customerLoginUrl> </root> customerLoginRole 表示当role=a的是否采用自定义登录界面,否则用默认, role=a加到URL上,比如: http://192.168.18.186:8080/webroot/decision?role=a  表示使用自定义登录,不加或者role不等于a的就使用默认登录
一年精通帆软报表之设计器菜单栏介绍之二
​  ​  如图所示,文件菜单下的选项包括:         新建普通报表,新建聚合报表等10几个菜单选项。 下面列一下每个菜单项点击时对应的Action,这些Action是在fine-report-designer.jar包中的com.fr.design.actions下,如图:​            文件菜单下的菜单选项对应的Action 菜单选型 Action 新建普通报表 NewWorkBookAction 新建聚合报表 NewPolyReportAction 新建决策报表 NewFormAction 打开 OpenTemplateAction 打开最近 OpenRecentReportMenuDef 这不是action,是一个下拉菜单 关闭 CloseCurrentTemplateAction 保存 SaveTemplateAction 另存为 SaveAsTemplateAction 撤销 UndoAction 重做 RedoAction 选项 PreferenceAction 切换工作目录 SwitchExistEnv   退出 ExitDesignerAction ​ 更多内容:https://blog.csdn.net/sixingbugai?spm=1000.2115.3001.5343
一年精通帆软报表之设计器菜单栏介绍之一
​  帆软报表设计器的菜单栏就是​以及​ 所示的内容, 前面已经说了这些菜单是如何,在哪创建的,当点击这些菜单项时,会调用一系列的Action,这些Action位于fine-report-designer.jar的com.fr.design.actions包下,如图: ​  下面就重点来介绍下这些Action。 1 所有的Action都继承自com.fr.design.actions.UpdateAction,这是一个抽象类。这个类实现了javax.swing.Action接口, 2 先来看看Javax.swing.Action接口          public void actionPerformed(ActionEvent e); public void setEnabled(boolean b); public boolean isEnabled(); public void putValue(String key, Object value); public Object getValue(String key); public void addPropertyChangeListener(PropertyChangeListener listener); public void removePropertyChangeListener(PropertyChangeListener listener); actionPerformed方法是ActionListener接口中的一个:实际上,Action接口扩展于ActionListener接口,因此,可以在任何需要ActionListener对象的地方使用Action对象。   setEnabled和isEnbaled两个方法允许启用或禁用这个动作,并检查这个动作当前是否启用。当一个连接到菜单或工具栏上的动作被禁用时,这个选项就会变成灰色。   putValue和getValue两个方法允许存储和检索动作对象中的任意名/值, 有两个重要的预定义字符串:Action.NAME和Action.SMALL_ICON,用于将动作的名字和图标存储到一个动作对象中: action.putValue(Action.NAME,"Blue"); action.putValue(Action.SMALL_ICON,new ImageIcon("blue-ball.gif")); 表1给出了所有预定义的动作表名称。                         表1 预定义动作表名称 表1 预定义动作表名称 名称 值 NAME 动作名称,显示在按钮和菜单上 SMALL_ICON 存储小图标的地方;显示在按钮、菜单项或工具栏中 SHORT_DESCRIPTION 图标的简要说明;显示在工具提示中 LONG_DESCRIPTION 图标的详细说明;使用在在线帮助中。没有Swing组件使用这个值 MNEMONIC_KEY 快捷键缩写;显示在菜单项中 ACCELERATOR_KEY 存储加速击键的地方;Swing组件不使用这个值 ACTION_COMMAND_KEY 历史遗留;仅在旧版本的registerKeyboardAction方法中使用 DEFALUT 常用的综合属性;Swing组件不使用这个值  如果动作对象添加到菜单或工具栏上,它的名称和图标就会被自动地提取出来,并显示在菜单项或工具栏项中。SHORT_DESCRIPTION值变成了工具提示。   addPropertyChangeListener和removePropertyChangeListener两个方法能够让其他对象在动作对象的属性发生变化时得到通告,尤其是菜单或工具栏触发的动作。例如,如果增加一个菜单,作为动作对象的属性变更监听器,而这个动作对象爱你个随后被禁用,菜单就会被调用,并将动作名称变为灰色。属性变更监听器是一种常用的构造形式,它是JavaBeans组件模型的一部分。   3 介绍完Action接口,下面具体看看UpdateAction的内容。          public abstract class UpdateAction extends ShortCut implements Action {         可以看出UpdateAction继承ShortCut类,实现了Action接口。      private boolean enabled = true; private Map<String, Object> componentMap; private String searchText = "";         componentMap存储了putValue放进来的值,也包括这个action管理的组件,enabled属性也控制componentMap中存储的组件的enabled属性,          public void setEnabled(boolean newValue) { boolean oldValue = this.enabled; if (oldValue != newValue) { this.enabled = newValue; Iterator valueIt = this.componentMap.values().iterator(); while(valueIt.hasNext()) { Object valueObject = valueIt.next(); if (valueObject instanceof JComponent) { ((JComponent)valueObject).setEnabled(this.enabled); } } } }         可以看出enabled属性控制着componentMap中的组件对象。      public void putValue(String key, Object newValue) { if (this.componentMap == null) { this.componentMap = new HashMap(); } if (newValue == null) { this.componentMap.remove(key); } else { this.componentMap.put(key, newValue); } }         存储key-value。      public UIMenuItem createMenuItem() { Object object = this.getValue(UIMenuItem.class.getName()); if (object == null && !(object instanceof UIMenuItem)) { UIMenuItem menuItem = new UIMenuItem(this); menuItem.setName(this.getName()); this.setPressedIcon4Button(menuItem); this.setDisabledIcon4Button(menuItem); object = menuItem; this.putValue(UIMenuItem.class.getName(), menuItem); } return (UIMenuItem)object; } 创建菜单项。 public UIMenuEastAttrItem createMenuItemEastAttr() { UIMenuEastAttrItem menuItem = new UIMenuEastAttrItem(this); menuItem.setName(this.getName()); this.setPressedIcon4Button(menuItem); this.setDisabledIcon4Button(menuItem); this.putValue(UIMenuItem.class.getName(), menuItem); return menuItem; } 创建 菜单East项。 public JComponent createToolBarComponent() { Object object = this.getValue(UIButton.class.getName()); if (!(object instanceof AbstractButton)) { UIButton button = null; button = new UIButton(); object = this.initButton(button, UIButton.class.getName()); } return (JComponent)object; } 创建工具栏按钮 public UpdateAction.UseMenuItem createUseMenuItem() { Object object = this.getValue(UpdateAction.UseMenuItem.class.getName()); if (object == null && !(object instanceof UpdateAction.UseMenuItem)) { object = new UpdateAction.UseMenuItem(this); this.putValue(UpdateAction.UseMenuItem.class.getName(), object); } this.setPressedIcon4Button((UpdateAction.UseMenuItem)object); this.setDisabledIcon4Button((UpdateAction.UseMenuItem)object); return (UpdateAction.UseMenuItem)object; } 创建 UseMenuItem。 public static UICheckBoxMenuItem createCheckBoxMenuItem(UpdateAction action) { UICheckBoxMenuItem menuItem = new UICheckBoxMenuItem(action.getName()); Integer mnemonicInteger = (Integer)action.getValue("MnemonicKey"); if (mnemonicInteger != null) { menuItem.setMnemonic((char)mnemonicInteger); } menuItem.setIcon((Icon)action.getValue("SmallIcon")); menuItem.addActionListener(action); menuItem.setToolTipText((String)action.getValue("LongDescription")); menuItem.setAccelerator((KeyStroke)action.getValue("AcceleratorKey")); return menuItem; }  创建ChechBox菜单选项。 从上可以看出Action可以创建出工具栏上的按钮,菜单上的选项等。 这个类里面有个跟拼音有关的帮助类: PinyinHelper.convertToPinyinString(title, "", PinyinFormat.WITHOUT_TONE)将中文转成拼音的帮助类。 里面还有一些其他方法,自己可以去看 ​ 更多内容:https://blog.csdn.net/sixingbugai?spm=1000.2115.3001.5343
一年精通帆软报表之设计器的属性面板源码解析
​  ​  EastRegionContainerPane就是这个区域。 1 com.fr.design.mainframe.EastRegionContainerPane          1.1 构造函数          private EastRegionContainerPane() { this.initPropertyItemList(); this.defaultPane = this.getDefaultPane(Toolkit.i18nText("Fine-Design_Basic_No_Settings_Available")); this.defaultAuthorityPane = this.getDefaultPane(Toolkit.i18nText("Fine-Design_Basic_Not_Support_Authority_Edit")); this.switchMode(EastRegionContainerPane.PropertyMode.REPORT); this.setContainerWidth(CONTAINER_WIDTH); this.initPluginPane(); this.listenPlugin(); }         1.1.1 initPropertyItemList,初始化了cellElement,cellAttr,floatElement,widgetSettings,conditionAttr,hyperlink,widgetLib,authorityEdition,editedRoles这几个大类的属性分类,就是​这个部分。         1.1.2 this.switchMode(EastRegionContainerPane.PropertyMode.REPORT);方法中会调用下面: private void initContentPane() { this.initRightPane(); this.initLeftPane(); }         initLeftPanel就是初始化界面左边​的界面,initRightPanel就是右边具体的属性面板。         1.1.3 initPluginPane 这里是一个插件扩展ExtraDesignClassManager classManager = (ExtraDesignClassManager)PluginModule.getAgent(PluginModule.ExtraDesign); Set providers = classManager.getArray("PropertyItemPaneProvider");。 ​ 更多内容:https://blog.csdn.net/sixingbugai?spm=1000.2115.3001.5343
一年精通帆软报表之设计器的中间部分的源码分析
​  ​  CenterRegionContainerPane管理的区域就是上图的区域。 1 他的构造函数          public CenterRegionContainerPane() { this.toolbarPane.setLayout(FRGUIPaneFactory.createBorderLayout()); this.eastPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); this.eastPane.add(this.largeToolbar = this.getToolBarMenuDock().createLargeToolbar(), "West"); this.eastCenterPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); this.combineUpTooBar(); this.eastCenterPane.add(this.combineUp, "North"); this.templateTabPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); this.templateTabPane.add(this.newWorkBookPane = this.getToolBarMenuDock().getNewTemplatePane(), "West"); this.templateTabPane.add(MutilTempalteTabPane.getInstance(), "Center"); this.eastCenterPane.add(this.templateTabPane, "Center"); this.eastPane.add(this.eastCenterPane, "Center"); this.toolbarPane.add(this.eastPane, "North"); this.toolbarPane.add(new UIMenuHighLight(), "South"); this.setLayout(new BorderLayout()); this.add(this.centerTemplateCardPane = new DesktopCardPane(), "Center"); this.add(this.toolbarPane, "North"); }         1.1 可以看出它使用BorderLayout布局,​  North放的是toolbarPane,Center放的是DesktopCardPane。         1.2 toolbarPane也是BorderLayout布局,它的North放的是eastPane,South放的是UIMenuHighLight。         1.3 eastPane也是BorderLayout布局,它的West放的是largeToolbar,Center放的是eastCenterPane,largeToolbar应该就是​           1.4 eastCenterPane也是BorderLayout布局,它的North放的是combineUp,Center放的是 templateTabPane,combineUp应该就是combineUp的创建过程中有一个插件扩展Set providers = ExtraDesignClassManager.getInstance().getArray("DesignerFrameUpButtonProvider");combineUp就是     templateTabPane也是BorderLayout布局,它的West放的是newWorkBookPane,他应该是​Center放的是MutilTempalteTabPane,MutilTempalteTabPane就是​ 这个部分。         2  com.fr.design.mainframe.DesktopCardPane就是中间表格的部分。重点介绍下          private JTemplate<?, ?> component; private TransparentPane transparentPane = new TransparentPane(); private OpenLoadingPane loadingPane = new OpenLoadingPane(); private OpenFailedPane failedPane = new OpenFailedPane(); private JLayeredPane layeredPane = new JLayeredPane()         2.1 component就是具体展现每张报表内容的组件,transparentPane,loadingPane,failedPane都是辅助层。         2.2 JTemplate代表某张报表 protected void showJTemplate(JTemplate<?, ?> jt) { if (((BaseBook)jt.getTarget()).getAttrMark("DesignCopyBanAttrMark") != null) { DesignModeContext.switchTo(DesignerMode.BAN_COPY_AND_CUT); } else if (!DesignModeContext.isVcsMode() && !DesignModeContext.isAuthorityEditing() && !DesignModeContext.isDuchampMode()) { DesignModeContext.switchTo(DesignerMode.NORMAL); } if (this.component != null) { this.component.fireTabChange(); } DesignerFrameFileDealerPane.getInstance().setCurrentEditingTemplate(jt); this.refresh(jt); }         显示某张报表内容。 ​
一年精通帆软报表之设计器的左边部分文件列表以及数据集的源码分析
​  ​  1 WestRegionContainerPane包含的就是上边两部分。上面文件列表是com.fr.design.mainframe.DesignerFrameFileDealerPane,下面数据集是com.fr.design.data.datapane.TableDataTreePane. 2 WestRegionContainerPane继承了UIResizableContainer,说明该区域大小可改变。 3 UIResizableContainer里面的组件构成:          private JComponent upPane; private JComponent downPane; private JComponent parameterPane; private UIResizableContainer.HorizotalToolPane horizontToolPane; private UIResizableContainer.VerticalToolPane verticalToolPane;   可以看出有三个组件:upPane,downPane,parameterPane,两个工具组件:horizontToolPane,verticalToolPane. 4 DesignerFrameFileDealerPane就放在了 upPane上,TableDataTreePane就放在了downPane上。parameterPane在这没有实例化。 5 DesignerFrameFileDealerPane:          private DesignerFrameFileDealerPane() { this.setLayout(new BorderLayout()); this.toolBar = ToolBarDef.createJToolBar(); this.toolBar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, UIConstants.TOOLBAR_BORDER_COLOR)); this.toolBar.setBorderPainted(true); JPanel tooBarPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); JPanel parent = new JPanel(new BorderLayout()); parent.add(this.toolBar, "Center"); parent.setBorder(BorderFactory.createEmptyBorder(3, 0, 4, 0)); tooBarPane.add(parent, "Center"); tooBarPane.add(new UIMenuHighLight(), "South"); this.add(tooBarPane, "North"); CardLayout card; JPanel cardPane = new JPanel(card = new CardLayout()); cardPane.add(TemplateTreePane.getInstance(), "file"); this.selectedOperation = TemplateTreePane.getInstance(); card.show(cardPane, "file"); TemplateTreePane.getInstance().setToolbarStateChangeListener(this); this.add(cardPane, "Center"); this.stateChange(); }         5.1 使用BorderLayout布局,​         5.2 Center放的是cardPane,North放的是tooBarPane         5.3 tooBarPane也使用的是BorderLayout布局,它的Center放的是parent的JPanel,South放的是UIMenuHighLight         5.4 cardPane使用CardLayout布局,他加入了TemplateTreePane作为子组件,这个组件应该是文件树组件。         5.5 parent的JPanel使用的也是BorderLayout,它的Center放的是toolBar。         5.6 toolBar上的那一排按钮是怎么加上去的。​,他们是在方法refreshDockingView上加的。          public void refreshDockingView() { ToolBarDef toolbarDef = new ToolBarDef(); toolbarDef.addShortCut(new ShortCut{this.newFolderAction, this.refreshTreeAction}); if (WorkContext.getCurrent().isLocal()) { toolbarDef.addShortCut(new ShortCut{this.showInExplorerAction}); } toolbarDef.addShortCut(new ShortCut{this.renameAction, this.delFileAction}); Set extraShortCuts = ExtraDesignClassManager.getInstance().getExtraShortCuts(); Iterator var3 = extraShortCuts.iterator(); while(var3.hasNext()) { ShortCut shortCut = (ShortCut)var3.next(); toolbarDef.addShortCut(new ShortCut{shortCut}); } this.addVcsAction(toolbarDef); toolbarDef.updateToolBar(this.toolBar); this.resetActionStatus(); this.refresh(); }        可以看出 addShortCut方法就是增加一个按钮,一共增加了newFolderAction,refreshTreeAction,showInExplorerAction,renameAction,delFileAction。这里还有一个插件扩展点Set extraShortCuts = ExtraDesignClassManager.getInstance().getExtraShortCuts();我们可以自己来添加自己的功能按钮到toolBar上。 6 TableDataTreePane:          private TableDataTreePane() { this.setLayout(new BorderLayout(4, 0)); this.setBorder((Border)null); this.dataTree = new TableDataTree(); ToolTipManager.sharedInstance().registerComponent(this.dataTree); ToolTipManager.sharedInstance().setDismissDelay(3000); ToolTipManager.sharedInstance().setInitialDelay(0); this.addMenuDef = new MenuDef(Toolkit.i18nText("Fine-Design_Basic_Action_Add")); this.addMenuDef.setIconPath("/com/fr/design/images/control/addPopup.png"); this.createAddMenuDef(); this.createPluginListener(); this.editAction = new TableDataTreePane.EditAction(); this.removeAction = new TableDataTreePane.RemoveAction(); this.previewTableDataAction = new PreviewTableDataAction(this, this.dataTree); this.connectionTableAction = new ConnectionTableAction(this); this.toolbarDef = new ToolBarDef(); this.toolbarDef.addShortCut(new ShortCut{this.addMenuDef, SeparatorDef.DEFAULT, this.editAction, this.removeAction, SeparatorDef.DEFAULT, this.previewTableDataAction, this.connectionTableAction}); UIToolbar toolBar = ToolBarDef.createJToolBar(); toolBar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, UIConstants.TOOLBAR_BORDER_COLOR)); toolBar.setBorderPainted(true); this.toolbarDef.updateToolBar(toolBar); JPanel toolbarPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); toolbarPane.add(toolBar, "Center"); this.add(toolbarPane, "North"); UIScrollPane scrollPane = new UIScrollPane(this.dataTree); scrollPane.setBorder((Border)null); this.initbuttonGroup(); JPanel jPanel = new JPanel(new BorderLayout(0, 0)); JPanel buttonPane = new JPanel(new GridLayout()); buttonPane.add(this.buttonGroup, "Center"); jPanel.add(buttonPane, "North"); jPanel.add(scrollPane, "Center"); this.add(jPanel, "Center"); this.dataTree.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { TableDataTreePane.this.checkButtonEnabled(); } }); this.dataTree.addKeyListener(this.getTableTreeNodeListener(this.editAction, this.previewTableDataAction, this.removeAction, this.op, this.dataTree)); this.dataTree.setEditable(true); TableDataTreeCellEditor treeCellEditor = new TableDataTreeCellEditor(this, new UITextField(), this.dataTree, this); treeCellEditor.addCellEditorListener(treeCellEditor); this.dataTree.setCellEditor(treeCellEditor); new TableDataTreeDragSource(this.dataTree, 1); this.checkButtonEnabled(); }         6.1 也是BorderLayout布局,North放的是toolbarPane,Center放的是一个jPanel的Panel。         6.2 toolbarPane里放的是toolBar,toolBar上增加了addMenuDef下拉菜单,分割线, editAction,removeAction,分割线,previewTableDataAction,connectionTableAction         6.3 jPanel里放到是buttonPane,scrollPane。scrollPanel放的是dataTree,dataTree应该就是我们定义的数据集树。         6.4 新增各种数据集的菜单在哪定义的呢?在createPluginListener中定义的      private void createPluginListener() { GeneralContext.listenPluginRunningChanged(new PluginEventListener(1) { public void on(PluginEvent event) { TableDataTreePane.this.addMenuDef.clearShortCuts(); TableDataTreePane.this.createAddMenuDef(); } }, new PluginFilter() { public boolean accept(PluginContext context) { return context.contain(PluginModule.ExtraDesign); } }); }         在TableDataTreePane.this.createAddMenuDef()中定义的。          protected void createAddMenuDef() { TableDataNameObjectCreator creators = TableDataCreatorProducer.getInstance().createReportTableDataCreator(); TableDataNameObjectCreator var2 = creators; int var3 = creators.length; for(int var4 = 0; var4 < var3; ++var4) { final TableDataNameObjectCreator creator = var2; if (creator.shouldInsertSeparator()) { this.addMenuDef.addShortCut(new LineSeparator()); } this.addMenuDef.addShortCut(new BasicTableDataTreePane.TDAction() { protected String getTDName() { return creator.menuName(); } protected Icon getTDIcon() { return creator.menuIcon(); } protected String getNamePrefix() { return creator.getPrefix(); } protected TemplateTableDataWrapper getTableDataInstance() { return new TemplateTableDataWrapper((TableData)creator.createObject()); } }); } } 重点是TableDataNameObjectCreator creators = TableDataCreatorProducer.getInstance().createReportTableDataCreator(); public TableDataNameObjectCreator createReportTableDataCreator() { TableDataNameObjectCreator dataBase = new TableDataNameObjectCreator(Toolkit.i18nText("Fine-Design_Basic_DS_Database_Query"), "ds", "/com/fr/design/images/data/database.png", DBTableData.class, DBTableDataPane.class); TableDataNameObjectCreator ds_Class = new TableDataNameObjectCreator(Toolkit.i18nText("Fine-Design_Basic_Tabledata_Type_Class"), "Class", "/com/fr/design/images/data/source/classTableData.png", ClassTableData.class, ClassTableDataPane.class); TableDataNameObjectCreator table = new TableDataNameObjectCreator(Toolkit.i18nText("Fine-Design_Basic_Tabledata_Type_Embedded"), "Embedded", "/com/fr/design/images/data/dataTable.png", EmbeddedTableData.class, EmbeddedTableDataPane.class); TableDataNameObjectCreator multiTable = new TableDataNameObjectCreator(Toolkit.i18nText("Fine-Design_Basic_Tabledata_Type_Relation"), "Multi", "/com/fr/design/images/data/multi.png", ConditionTableData.class, MultiTDTableDataPane.class) { public boolean isNeedParameterWhenPopulateJControlPane() { return true; } }; TableDataNameObjectCreator fileTable = new TableDataNameObjectCreator(Toolkit.i18nText("Fine-Design_Basic_Tabledata_Type_File"), "File", "/com/fr/design/images/data/file.png", FileTableData.class, FileTableDataSmallHeightPane.class); TableDataNameObjectCreator treeTable = new TableDataNameObjectCreator(Toolkit.i18nText("Fine-Design_Basic_Tabledata_Type_Tree"), "Tree", "/com/fr/design/images/data/tree.png", RecursionTableData.class, TreeTableDataPane.class) { public boolean isNeedParameterWhenPopulateJControlPane() { return true; } }; TableDataNameObjectCreator storeProcedure = new TableDataNameObjectCreator(Toolkit.i18nText("Fine-Design_Basic_Datasource_Stored_Procedure"), "Proc", "/com/fr/design/images/data/store_procedure.png", StoreProcedure.class, ProcedureDataPane.class) { public boolean shouldInsertSeparator() { return true; } }; TableDataNameObjectCreator creators = new TableDataNameObjectCreator{dataBase, ds_Class, table, fileTable, storeProcedure, multiTable, treeTable}; return this.merge(creators, ExtraDesignClassManager.getInstance().getReportTableDataCreators()); } 这里面定义了集中数据集,还有一个插件扩展点ExtraDesignClassManager.getInstance().getReportTableDataCreators(),扩展名TableDataDefineProvider。 ​ 更多内容:https://blog.csdn.net/sixingbugai?spm=1000.2115.3001.5343
帆软报表重要NorthRegionContainerPane 主要是设计器菜单栏的部分
​  前面分析了帆软报表设计器主界面采用了BorderLayout布局,如下图:         ​  1 NORTH布局部分,这个部分放到的NorthRegionContainerPane。他是设计器的Menu部分。下面来分析下这个类。         1.1 构造函数                  public NorthRegionContainerPane() { ToolBarMenuDock ad = DesignerContext.getDesignerFrame().getToolBarMenuDock(); this.setLayout(new BorderLayout()); this.add(new UIMenuHighLight(), "South"); this.add(this.initNorthEastPane(ad), "East"); }         可以看出这个类也是BorderLayout布局,South放的是UIMenuHighLight,这个应该是Menu的高亮部分 。         1.2 initNorthEastPane方法                  protected JPanel initNorthEastPane(final ToolBarMenuDock ad) { final JPanel northEastPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); GeneralContext.listenPluginRunningChanged(new PluginEventListener(-1) { public void on(PluginEvent event) { NorthRegionContainerPane.this.refreshNorthEastPane(northEastPane, ad); SwingUtilities.invokeLater(new Runnable() { public void run() { if (DesignerContext.getDesignerFrame() != null) { DesignerContext.getDesignerFrame().refresh(); DesignerContext.getDesignerFrame().repaint(); } } }); } }, new PluginFilter() { public boolean accept(PluginContext context) { return context.contain(PluginModule.ExtraDesign); } }); this.refreshNorthEastPane(northEastPane, ad); return northEastPane; }         上面创建的northEastPane,就是一个BorderLayout布局的空JPanel。         1.3 看下refreshNorthEastPane                  private void refreshNorthEastPane(final JPanel northEastPane, final ToolBarMenuDock ad) { northEastPane.removeAll(); northEastPane.setLayout(new FlowLayout(2, 0, 0)); northEastPane.add(LogMessageBar.getInstance()); TitlePlaceProcessor processor = (TitlePlaceProcessor)ExtraDesignClassManager.getInstance().getSingle("TitlePlaceProcessor"); if (processor != null) { final Component bbsLoginPane = new Component{null}; OSSupportCenter.buildAction(new OSBasedAction() { public void execute(Object... objects) { bbsLoginPane = ad.createBBSLoginPane(); } }, SupportOSImpl.USERINFOPANE); processor.hold(northEastPane, LogMessageBar.getInstance(), bbsLoginPane); } northEastPane.add(ad.createAlphaFinePane()); if (!DesignerEnvManager.getEnvManager().getAlphaFineConfigManager().isEnabled()) { ad.createAlphaFinePane().setVisible(false); } northEastPane.add(ad.createNotificationCenterPane()); OSSupportCenter.buildAction(new OSBasedAction() { public void execute(Object... objects) { northEastPane.add(ad.createBBSLoginPane()); } }, SupportOSImpl.USERINFOPANE); }         将northEastPane的BorderLayout布局改为FlowLayout布局了,这个布局是将组件按照设置对齐方式从左向右排列,一行排满到下一行继续排列。这里对齐方式是2,表示从右往左。         northEastPane.add(ad.createAlphaFinePane());         northEastPane.add(ad.createNotificationCenterPane());         northEastPane.add(ad.createBBSLoginPane())                 上面是增加三个JLabel组件, 从上面可以看出 这个部分应该就是设计器的:         ​  那么菜单那部分UI在哪创建的呢?         其实他是在DesignerFrame的initMenuPane方法中创建的。                  protected void initMenuPane() { this.basePane.add(NorthRegionContainerPane.getInstance(), "North"); this.resetToolkitByPlus((ToolBarMenuDockPlus)null); }         他是在this.resetToolkitByPlus((ToolBarMenuDockPlus)null)创建的。这个方法会调用NorthRegionContainerPane.getInstance().resetToolkitByPlus(plus, this.ad)来创建的。具体的创建代码为:          public MenuDef menus(ToolBarMenuDockPlus plus) { clearPluginListeners(); final List menuList = new ArrayList(); menuList.add(this.createFileMenuDef(plus)); MenuDef menuDefs = this.createTemplateShortCuts(plus); this.insertTemplateExtendMenu(plus, menuDefs); menuList.addAll(Arrays.asList(menuDefs)); if (WorkContext.getCurrent() != null && WorkContext.getCurrent().isRoot()) { menuList.add(this.createServerMenuDef(plus)); } menuList.add(this.createHelpMenuDef()); LocaleCenter.buildAction(new LocaleAction() { public void execute() { ToolBarMenuDock.this.addCommunityMenuDef(menuList); } }, SupportLocaleImpl.COMMUNITY); this.addAllUpdateActionsToList(menuList); UpdateActionManager.getUpdateActionManager().setUpdateActions(this.shortCutsList); return (MenuDef)menuList.toArray(new MenuDef); }         可以看出 createFileMenuDef创建File一级菜单,createTemplateShortCuts创建模板一级菜单,createTemplateShortCuts的调用过程中,会调用com.fr.design.mainframe.ElementCasePaneDelegate的createInsertMenuDef,createCellMenuDef来创建插入,单元格菜单。  createServerMenuDef创建服务器一级菜单,createHelpMenuDef创建帮助一级菜单, 下面看看创建File菜单的过程:          public MenuDef createFileMenuDef(ToolBarMenuDockPlus plus) { MenuDef menuDef; if (DesignerMode.isVcsMode()) { menuDef = VcsScene.createFileMenuDef(plus); this.insertMenu(menuDef, "file"); return menuDef; } else { menuDef = new MenuDef(Toolkit.i18nText("Fine-Design_Basic_File"), 'F'); ShortCut scs = new ShortCut; if (!DesignerMode.isAuthorityEditing()) { scs = this.createNewFileShortCuts(); } if (!ArrayUtils.isEmpty(scs)) { menuDef.addShortCut(scs); } menuDef.addShortCut(this.openTemplateAction()); menuDef.addShortCut(new OpenRecentReportMenuDef()); this.addCloseCurrentTemplateAction(menuDef); scs = plus.shortcut4FileMenu(); if (!ArrayUtils.isEmpty(scs)) { menuDef.addShortCut(SeparatorDef.DEFAULT); menuDef.addShortCut(scs); menuDef.addShortCut(SeparatorDef.DEFAULT); } this.addPreferenceAction(menuDef); this.addSwitchExistEnvAction(menuDef); menuDef.addShortCut(new ExitDesignerAction()); this.insertMenu(menuDef, "file"); return menuDef; } }    首先这个方法是在com.fr.design.mainframe.toolbar.ToolBarMenuDock类中,    创建菜单项的类是:MenuDef   File菜单下的是通过 , 一共给File菜单添加了8个Action:         NewWorkBookAction,NewPolyReportAction,OpenTemplateAction,OpenRecentReportMenuDef,CloseCurrentTemplateAction,PreferenceAction,SwitchExistEnv,ExitDesignerAction。  所有的其他Action都在 fine-report-designer的com.fr.design.actions包下,看图:         ​          这些Action就是菜单栏上的功能,后面可以详细研究下 ​    更多内容:https://blog.csdn.net/sixingbugai?spm=1000.2115.3001.5343
帆软报表重要Activator之DesignerStartup中的GlobalListenerProvider扩展开发
​  上一篇我们说了DesignerStartup的启动过程中有一个GlobalListenerProvider扩展,这个扩展是一个监听键盘事件的监听扩展。先看效果:         ​          当监听到键盘事件时,弹出提示。 1 先看插件项目结构:         ​          需要实现两个类,一个是provider,一个是listener 2 plugin.xml中的配置:         ​          这个扩展是配置到extra-designer节点下,扩展xml标签名字为:GlobalListenerProvider   3 查看源码  3.1 MyGlobalListenerProvider1源码          package com.fr.plugin.helloworld.globallistenerprovider; import com.fr.design.fun.impl.AbstractGlobalListenerProvider; import java.awt.*; import java.awt.event.AWTEventListener; /* * 这个扩展是通过Toolkit.getDefaultToolkit().addAWTEventListener方法将AWTEventListener事件加入的,这个是键盘事件. * * */ public class MyGlobalListenerProvider1 extends AbstractGlobalListenerProvider { @Override public AWTEventListener listener() { // Toolkit.getDefaultToolkit().addAWTEventListener(); return new MyAWTEventListener1(); } } 这个类继承了抽象类AbstractGlobalListenerProvider,AbstractGlobalListenerProvider类实现了接口GlobalListenerProvider接口,帆软报表中对于扩展插件的开发,一般内部都会实现一个抽象类,我们只需要继承这个抽象类即可,而不必去实现扩展接口。 这个类要求我们返回一个AWTEventListener类型的实例对象。这里返回我们自己实现的MyAWTEventListener1实例。 3.2 MyAWTEventListener1源码          package com.fr.plugin.helloworld.globallistenerprovider; import com.fr.log.FineLoggerFactory; import com.fr.log.FineLoggerProvider; import javax.swing.*; import java.awt.*; import java.awt.event.AWTEventListener; import java.awt.event.KeyEvent; //键盘事件 public class MyAWTEventListener1 implements AWTEventListener { private static final FineLoggerProvider LOGGER = FineLoggerFactory.getLogger(); @Override public void eventDispatched(AWTEvent event) { if(event instanceof KeyEvent){ KeyEvent ke = (KeyEvent)event; LOGGER.info("发生AWT事件了:"+ke.getSource()); LOGGER.info("发生AWT事件了:"+ke.getKeyCode()); LOGGER.info("发生AWT事件了:"+ke.getKeyChar()); LOGGER.info("发生AWT事件了:"+ke.getID()); LOGGER.info("发生AWT事件了:"+ke.paramString()); JOptionPane.showMessageDialog(null, "你按了键盘的:"+ke.getKeyChar()); } LOGGER.debug("发生AWT事件了:"+event.toString()); } }         MyAWTEventListener1类我们实现了AWTEventListener接口,并且判断event是否是KeyEvent的实例,并且弹出一个提示框。 ​ 更多内容:https://blog.csdn.net/sixingbugai?spm=1000.2115.3001.5343
帆软报表重要Activator之DesignerStartup
​  com.fr.start.module.DesignerStartup位于fine-report-designer.jar中,也是designer-startup.xml中的第一个activator,也是根activator。它主要处理设计器界面相关的功能. 1 beforeAllStart方法: public void beforeAllStart() { BuildContext.setBuildFilePath("/com/fr/stable/build.properties"); this.checkDebugStart(); DesignerEnvManager.getEnvManager(); DesignUtils.initLookAndFeel(); if (DesignUtils.isPortOccupied()) { StartErrorMessageCollector.getInstance().record(DesignerErrorMessage.PORT_OCCUPIED.getId(), DesignerErrorMessage.PORT_OCCUPIED.getMessage()); DesignerPort.getInstance().resetPort(); } if (DesignUtils.isStarted()) { String args = ((StartupArgs)this.startupArgsValue.getValue()).get(); if (ArrayUtils.isNotEmpty(args)) { DesignUtils.clientSend(args); } FineLoggerFactory.getLogger().info("The Designer Has Been Started"); if (args.length == 0) { TipDialog dialog = new TipDialog((Frame)null, DesignerProcessType.INSTANCE.obtain(), Toolkit.i18nText("Fine-Design_Last_Designer_Process_Not_Exist"), Toolkit.i18nText("Fine-Design_End_Occupied_Process"), Toolkit.i18nText("Fine-Design_Basic_Cancel")) { protected void endEvent() { this.dispose(); DesignUtils.clientSend(new String{"end"}); RestartHelper.restart(); } protected void cancelEvent() { this.dispose(); } }; dialog.setVisible(true); StartErrorMessageCollector.getInstance().record(DesignerErrorMessage.DESIGNER_PROCESS_OCCUPIED.getId(), DesignerErrorMessage.DESIGNER_PROCESS_OCCUPIED.getMessage(), ""); FineLoggerFactory.getLogger().error(DesignerErrorMessage.DESIGNER_PROCESS_OCCUPIED.getId() + ": " + DesignerErrorMessage.DESIGNER_PROCESS_OCCUPIED.getMessage()); } DesignerExiter.getInstance().execute(); } else { UIUtil.invokeAndWaitIfNeeded(new Runnable() { public void run() { SplashContext.getInstance().registerSplash(DesignerStartup.this.createSplash()); SplashContext.getInstance().show(); } }); } } 该方法中:         DesignerEnvManager.getEnvManager()                 处理设计器环境功能,首先加载我们电脑中的C:\Users\你的用户名\.FineReport100\FineReportEnv.xml这个xml文件,这个大家可以去查看自己电脑中的这个文件。DesignerEnvManager是专门管理设计器环境变量的类(后面可以重点研究下)         DesignUtils.initLookAndFeel()                 设置java swing的UIManager的lookAndFeel为UILookAndFeel,这个类继承自MetalLookAndFeel。设置字体。         SplashContext.getInstance().registerSplash(DesignerStartup.this.createSplash())                 创建启动画面,启动界面类是SplashCommon,SplashWindow。界面相关的图片都在 fine-report-engine.jar包的com/fr/base/images下。   2 start方法:          public void start() { this.startSub(PreStartActivator.class); this.startSub("parallel"); this.browserDemoIfNeeded(); this.startupEmbedServerIfNeeded(); }  启动子Activator,启动tomcat。 来看看如何用代码来创建tomcat          private void initTomcat() { this.tomcat = new Tomcat(); this.tomcat.setPort(DesignerEnvManager.getEnvManager().getEmbedServerPort()); this.tomcat.getConnector().setURIEncoding("UTF-8"); this.tomcat.getConnector().setProperty("relaxedQueryChars", "|{}^&#x5c;&#x60;&quot;&lt;&gt;"); this.setMaxPostSize(); this.setMaxHttpHeaderSize(); String docBase = (new File(WorkContext.getCurrent().getPath())).getParent(); String contextPath = "/" + ProductConstants.getAppFolderName(); Context context = this.tomcat.addContext(contextPath, docBase); context.setResources(new StandardRoot(context)); Wrapper servlet = Tomcat.addServlet(context, "DruidStatView", "com.fr.third.alibaba.druid.support.http.StatViewServlet"); context.addServletMappingDecoded("/druid/*", "DruidStatView"); servlet.setLoadOnStartup(1); servlet.setOverridable(true); Tomcat.initWebappDefaults(context); context.setLoader(new FineEmbedServerActivator.FRTomcatLoader()); SpringServletContainerInitializer initializer = new SpringServletContainerInitializer(); Set classes = new HashSet(); classes.add(FineWebApplicationInitializer.class); context.addServletContainerInitializer(initializer, classes); }         这里的Tomcat是Apache的类。org.apache.catalina.startup.Tomcat   3 afterAllStart方法          public void afterAllStart() { GlobalListenerProviderManager.getInstance().init(); StartupMessageCollector.getInstance().recordStartupLog(); }         看下GlobalListenerProviderManager的init方法          public void init() { Set providers = ExtraDesignClassManager.getInstance().getArray("GlobalListenerProvider"); this.addAWTEventListeners(providers); this.listenPlugin(); }         这里出现了一个扩展GlobalListenerProvider,如果咋们的插件项目做一个GlobalListenerProvider的扩展,这里是将扩展里的监听对象加入到 Toolkit.getDefaultToolkit().addAWTEventListener(listener, 8L); 这是监听键盘事件。 最后DesignerStartup就先分析到这,下一步来写一个插件实现GlobalListenerProvider扩展,看看。 ​ 更多内容:https://blog.csdn.net/sixingbugai?spm=1000.2115.3001.5343
深入了解帆软报表系统的启动过程二
​  前面说到MainDesigner的main方法中通过ModuleContext来加载解析designer-startup.xml的。 下面详细说说具体过程。 1 ModuleContext位于fine-core.jar包下,com.fr.module.ModuleContext,看一下它的parseRoot方法 public static synchronized Module parseRoot(String var0) { assert StringUtils.isNotBlank(var0) && !var0.contains(".."); var0 = "/com/fr/config/starter/" + var0; Module var1 = FineModuleParser.parse(var0); ModuleContext.ModuleAccessor.init(var1); return var1; } 这里看到实际的解析是由FineModuleParser来完成的。 2 com.fr.module.engine.build.FineModuleParser的parse方法: public static Module parse(String var0) { ModuleConfig var1 = ModuleConfigFactory.create().downParse(var0); return var1 == null ? null : ModuleBuilder.build(var1); } 可以看到将配置文件的内容封装到ModuleConfig对象中了。然后使用ModuleBuilder来build模块. 3 com.fr.module.engine.build.ModuleBuilder的build方法          static FineModule build(ModuleConfig var0) { FineModule var1 = build(var0, (Module)null); if (var1 == null) { return null; } else { finishLink(var1); ActivatorExtension.executePrepare(var1); return var1; } }        从上面可以看出,先调用内部的build方法得到根模块,finishLink是将所有的模块用链表的格式连接起来。ActivatorExtension.executePrepare(var1),是如果activator实现了com.fr.module.extension.Prepare接口,就调用一下prepare方法,这个接口的作用是在调用activator的start方法前做一些准备工作。 看一下内部的 build方法:          private static FineModule build(ModuleConfig var0, Module var1) { Activator var2; try { var2 = createActivator(var0.getAttribute(ModuleAttribute.Activator)); } catch (ClassNotFoundException var4) { FineLoggerFactory.getLogger().debug("Module not found {}", new Object{var0.getName()}); return null; } catch (Throwable var5) { logError(var0, var5); return null; } FineModule var3 = FineModule.create(var0.getName(), var2, var1, var0.getAttributes()); buildChildren(var3, var0); return var3; } 从上面可以看出,首先创建Activator实例,然后创建module,然后再构建子module。 看一下createActivator:          private static Activator createActivator(String var0) throws ClassNotFoundException, IllegalAccessException, InstantiationException { if (StringUtils.isBlank(var0)) { return new VirtualActivator(); } else { Class var1 = Class.forName(var0); return (Activator)var1.newInstance(); } } 这很简单,就是根据类名来创建实例. 看一下FineModule.create public FineModule(@NotNull String var1, @NotNull Activator var2, @Nullable Module var3, @NotNull Map<ModuleAttribute, String> var4) { assert StringUtils.isNotBlank(var1); this.name = var1; this.activator = var2; this.parent = var3; this.attributes = var4; this.invokeSubStrategy = InvokeSubStrategyFactory.create(this.getAttribute(ModuleAttribute.InvokeSubsStrategy)); this.runner = new FineModule.FineModuleRunner(); } public static FineModule create(@NotNull String var0, @NotNull Activator var1, @Nullable Module var2, @NotNull Map<ModuleAttribute, String> var3) { FineModule var4 = new FineModule(var0, var1, var2, var3); ContextImpl.createAndAttach(var4); return var4; } 上面可以看出是直接new一个FineModule实例与这个Activator实例关联上,然后这个module又与一个Context关联上,Context的概念后面讲,看看Context是如何跟Module关联的。 public static void createAndAttach(FineModule var0) { ContextImpl var1 = new ContextImpl(); var1.attach(var0); } private void attach(FineModule var1) { this.setModule(var1); var1.attachContext(this); var1.getActivator().attachContext(this); this.executor = new FineExecutorImpl(var1.getClass()); if (this.isRoot()) { this.validators = new ConcurrentHashMap(4); } else { this.validators = null; } var1.onStop(new ListenerAdaptor() { protected void on(Event var1) { ContextImpl.this.executor.shutdownThreadPool(); } }); } 从上面看出,也是new一个ContextImpl实例来与这个Module实例关联的,activator又与Context实例关联上。 从上面看出这里有三个概念,Module,Activator,Context,这三者通过上面的过程就关联上了。   更多内容:https://blog.csdn.net/sixingbugai?spm=1000.2115.3001.5343 ​
深入了解帆软报表系统的启动过程一
​  1 帆软报表的启动类为:com.fr.learn.Learner,它的main方法如下: public static void main(String args) { try { Class mainClass = Class.forName("com.fr.start.MainDesigner"); invokeMain(mainClass, args); } catch (ClassNotFoundException e) { // MainDesigner找不到,走以前的Designer try { Class oldMainClass = Class.forName("com.fr.start.Designer"); invokeMain(oldMainClass, args); } catch (ClassNotFoundException ex) { FineLoggerFactory.getLogger().error(e.getMessage(), e); } } }         从上面可以看出,首先会d加载com.fr.start.MainDesigner类,如果找不到,就加载com.fr.start.Designer。这两个类都在fine-report-designer.jar中 2 com.fr.start.MainDesigner的main方法:          public static void main(String args) { StopWatch watch = new StopWatch(); watch.start(); DesignerLifecycleMonitorContext.getMonitor().beforeStart(); FineRuntime.start(); DesignerSubListener.getInstance().start(); EventDispatcher.listen(LifecycleErrorEvent.SELF, new Listener<FineLifecycleFatalError>() { public void on(Event event, FineLifecycleFatalError param) { LifecycleFatalErrorHandler.getInstance().handle(param); } }); Module designerRoot = ModuleContext.parseRoot("designer-startup.xml"); designerRoot.setSingleton(StartupArgs.class, new StartupArgs(args)); try { designerRoot.start(); } catch (FineLifecycleFatalError var4) { LifecycleFatalErrorHandler.getInstance().handle(var4); } if (WorkContext.getCurrent().isLocal()) { ServerTray.init(); } FineLoggerFactory.getLogger().info("Designer started.Time used {} ms", new Object{watch.getTime()}); watch.stop(); } 关键代码为:          Module designerRoot = ModuleContext.parseRoot("designer-startup.xml"); designerRoot.setSingleton(StartupArgs.class, new StartupArgs(args)); try { designerRoot.start(); } catch (FineLifecycleFatalError var4) { LifecycleFatalErrorHandler.getInstance().handle(var4); } 加载解析配置文件designer-startup.xml,这个文件在fine-activator.jar中。解析后会得到根模块,然后调用根模块的start方法,这会导致配置文件中的一系列的Activator的start方法也开始运行。 3 看一下这个jar包中的结构:         ​ 我们看到在com.fr.config.starter下有两个xml文件,designer-startup.xml与server-startuo.xml,这就是帆软报表整体的配置文件,designer-startup.xml是在设计器模式下加载的配置,server-startuo.xml是非设计器模式下加载的配置。其他目录的xml配置文件都会被这两个配置文件来引用。 3.1 初略看一下designer-startup.xml中的内容:          <?xml version="1.0" encoding="UTF-8"?> <designer-startup activator="com.fr.start.module.DesignerStartup" invoke-subs-strategy="custom" role="root"> <!--启动前的准备工作--> <pre-start activator="com.fr.start.module.PreStartActivator" invoke-subs-strategy="parallel"> <basic ref="../base/basic.xml"/> </pre-start> <parallel invoke-subs-strategy="parallel"> <designer-init activator="com.fr.start.module.DesignerInitActivator"/> <workspace activator="com.fr.start.module.DesignerWorkspaceActivator" invoke-subs-strategy="subs-first"> <!--环境相关模块--> <designer-workspace-provider activator="com.fr.start.module.DesignerWorkspaceProvider" role="workspace-provider"/> <!--基于env的模块合集--> <env-based activator="com.fr.start.module.EnvBasedModule" invoke-subs-strategy="subs-first"> <!--core--> <core ref="../base/core.xml"/> <!--数据源模块,目前还没有启动逻辑,只是注册一下模块版本信息--> <parallel invoke-subs-strategy="parallel"> <designer-show activator="com.fr.start.module.DesignerShowActivator"/> <function> <datasource activator="com.fr.data.DatasourceActivator" version="10.0"/> <!--功能基础--> <function-base ref="../function/function-base.xml"/> <designer ref="../designer/designer.xml"/> <!--内置服务器功能模块--> <emb-server activator="com.fr.start.server.FineEmbedServerActivator" invoke-subs-strategy="custom" auto-invoke-by-parent="false" binding-workspace="local"> <server ref="../server/server.xml"/> </emb-server> </function> </parallel> </env-based> </workspace> </parallel> </designer-startup> 我们发现这里面定义了许多activator,也引用了其他的xml文件,activator这里可以理解为模块,最外层就是根模块,模块也包含父子关系,兄弟关系。 com.fr.start.MainDesigner中使用ModuleContext来解析这个配置文件,并返回一个根Module对象,在解析过程中,将这些模块已经按层次关系组装好了。 当得到根Module之后,就会调用根模块的start方法。 designerRoot.start(); 这不仅仅是调用了根模块的start方法,其他的模块的start方法他也一并会调用。 这里有一个对用关系,一个module对应一个activator,帆软报表将所有功能都分配到各个activator里了。 这就是帆软报表的启动过程,这里分析得很简单,后面会详细来分析内部过程。 ​
12下一页
个人成就
内容被浏览151,774
加入社区4年9天
返回顶部