计划等项目小结后再来分享项目得, 看到有不少问这个的,就先分享出一文章吧.
@JFinal 老大,帮忙检查一下问题和补充下
自动切换:
Model的切换:
3.0 中 Model.getConfig() 的可见性由 private 改为 protected,
所以在你的 BaseModel 中重写这一个,不动其他代码 就搞定了Model自动切换数据源,
@Override protected Config getConfig() { String configName = WebsiteInterceptor.getConfigName(); if(configName == null) configName = DbKit.MAIN_CONFIG_NAME; return DbKit.getConfig(configName); }
Db的切换:
我用Ctrl + H 进行全局Db.替换为: Db.use(getConfigName()). 或者db().
PS:先搜索"Db.use(" 看系统中有这么用的没有,如果有先处理掉
我的各个Base层都有getConfigName()这个方法,
当然都是调的 WebsiteInterceptor.getConfigName();
各个BaseXxx层:
/*** * @Title: 获取访问者的 ConfigName * @return String 本次访问的ConfigName */ public String getConfigName(){ return WebsiteInterceptor.getConfigName(); } public DbPro db(){ return Db.use(getConfigName()); }
拦截器WebsiteInterceptor:
import javax.servlet.http.HttpServletRequest; import com.jfinal.aop.Interceptor; import com.jfinal.aop.Invocation; import com.jfinal.core.Controller; import com.jfinal.kit.PropKit; import com.jfinal.log.Log; import com.momathink.common.constants.DictKeys; import com.momathink.common.service.ActiveRecordPluginService; /** * @ClassName: 访问者的ConfigName管理 * @author dufuzhong@126.com * @date 2016年10月3日 下午2:33:33 */ public class WebsiteInterceptor implements Interceptor { private static final Log log = Log.getLog(WebsiteInterceptor.class); private static final ThreadLocal<String> ME_CONFIGNAME = new ThreadLocal<String>(); public static final String SERVER_NAME = "serverName"; public static final String CONFIG_NAME = "configName"; /*** * @Title: 获取访问者的 ConfigName * @return String 本次访问的ConfigName */ public static String getConfigName(){ return ME_CONFIGNAME.get(); } /* nginx 配置 内容: location xxxx { proxy_set_header Host $host:80; proxy_set_header X-Forwarded-For $remote_addr; proxy_redirect off; proxy_pass http://127.0.0.1:8080; # 配置多台tomcat 就 改 端口号等 } */ @Override public void intercept(Invocation inv) { Controller controller = inv.getController(); HttpServletRequest request = controller.getRequest(); String ipFromNginx = controller.getHeader("X-Forwarded-For"); String serverName = request.getServerName(); /* 如果你的数据源是已知固定的 在启动JFinal的时候 用 serverName 做数据源的 configName 也就是说他们是相等的, 到这里就可以完结了, 如果是动态的,需要看后的动态管理方式 * */ // String configName = serverName; 下面的判断也 改成 if(DbKit.getConfig(configName) != null){ String configName = ActiveRecordPluginService.me.getConfigName(serverName); controller.setAttr(SERVER_NAME, serverName); controller.setAttr(CONFIG_NAME, configName); log.debug("访问者: 域名=" + serverName + " 资源K=" + configName + " IP=" + ipFromNginx); if(configName != null){ ME_CONFIGNAME.set(configName); //异常必须在里面的拦截器进行捕捉处理 inv.invoke(); ME_CONFIGNAME.remove(); } else { controller.renderError(403); } } }
如果你的数据源是已知固定的 在启动JFinal的时候
用 serverName 做数据源的 configName 也就是说他们是相等的,
到这里就可以完结了, 如果是动态的,需要看后的动态管理方式
动态管理:
ActiveRecordPluginService管理控制
import java.net.URLEncoder; import java.util.HashMap; import java.util.List; import java.util.Map; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.jfinal.kit.HttpKit; import com.jfinal.kit.JsonKit; import com.jfinal.kit.PropKit; import com.jfinal.kit.Ret; import com.jfinal.log.Log; import com.jfinal.plugin.IPlugin; import com.jfinal.plugin.activerecord.ActiveRecordPlugin; import com.jfinal.plugin.activerecord.CaseInsensitiveContainerFactory; import com.jfinal.plugin.activerecord.DbKit; import com.jfinal.plugin.activerecord.cache.EhCache; import com.jfinal.plugin.druid.DruidPlugin; import com.momathink.api.ApiInterceptor; import com.momathink.common.constants.DictKeys; import com.momathink.common.model.Site; /**(域名白名单) * <br> * 多站点配置信息和多数据源 管理 * @author dufuzhong@126.com * @date 2016年11月8日 下午19:16:23 */ public class ActiveRecordPluginService implements IPlugin { private static final Log log = Log.getLog(ActiveRecordPluginService.class); public static final ActiveRecordPluginService me = new ActiveRecordPluginService(); /*** * 存储 多站点信息, 域名为 键, 数据源configName 为 值 */ private static final Map<String, String> CONFIGNAME_S = new HashMap<String, String>(); static{//初始化值 CONFIGNAME_S.put("localhost", DbKit.MAIN_CONFIG_NAME); CONFIGNAME_S.put("127.0.0.1", DbKit.MAIN_CONFIG_NAME); } private ActiveRecordPluginService(){} public static ActiveRecordPluginService me() { return me; } /*** * @Title: 获取某站点 数据源configName * @param key 访问者域名 * @return String 该站点数据源configName */ public String getConfigName(String key) { return CONFIGNAME_S.get(key); } /*** * 加载 多站点配置信息 * @return */ public synchronized boolean load(List<Site> sites){ log.info("加载 多站点配置信息"); for (Site site : sites) add(site); //报告记录一下启动情况 String url = PropKit.get(DictKeys.SITE_SETCONFIGSITE).trim() + ApiInterceptor.getMaskKit(); try { String data = URLEncoder.encode(JsonKit.toJson(sites), "UTF-8"); log.info("报告多站点多数据源配置信息网站地址: "+ url); String post = HttpKit.post(url, "&data=" + data); log.info("报告完毕:" + post); } catch (Exception e) { log.info("报告异常,可能是没有启动运维服务器, 异常信息:" + e.getMessage()); return false; } return true; } /*** * 动态 配置 数据库参数 和 加载系统资源 */ public synchronized void add(Site site) { log.info("解析内容:" + site); //加 try catch 的原因是 不能因为某个 站点 的错误 配置信息, 影响到 其他的站点 try { //配置 数据库参数 和 加载系统资源 Ret addDb = init(site); //数据库启动载入, 启动成功则连接成功, 否则会异常 boolean druidPlugin = ((DruidPlugin) addDb.get("druidPlugin")).start(); boolean arp = ((ActiveRecordPlugin) addDb.get("arp")).start(); //连接池和管理器都要成功true 才能通过 if(!(druidPlugin) || !(arp)) throw new Exception("连接池和管理器"); //网址域名 String website = site.getWebsite().trim(); //该网站的系统资源 KEY 值 String configname = site.getConfigName().trim(); //存储 该站点资源KEY信息 CONFIGNAME_S.put(website, configname); //成功返回码 site.keep("id"); site.set(ApiInterceptor.ERRJSON_ERRCODE, 0).set(ApiInterceptor.ERRJSON_ERRMSG, "OK"); } catch (Exception e) { //错误码表 后面再写... 先直接 看错误信息吧 site.set(ApiInterceptor.ERRJSON_ERRCODE, 500).set(ApiInterceptor.ERRJSON_ERRMSG, e.getMessage()); log.info("配置 数据库参数 错误信息:" + site); } } /*** * 移除 动态 配置 数据库参数 和 加载的系统资源 */ public synchronized void del(Site site) { CONFIGNAME_S.remove(site.getWebsite()); DbKit.removeConfig(site.getConfigName()); //成功返回码 site.keep("id"); site.set(ApiInterceptor.ERRJSON_ERRCODE, 0).set(ApiInterceptor.ERRJSON_ERRMSG, "OK"); } /**配置数据库参数 和 加载系统资源 */ private Ret init(Site site) { //该网站的系统资源 KEY 值 String configname = site.getConfigName().trim(); String jdbcurl = site.getJdbcUrl().trim(); String user = site.getUser().trim(); String password = site.getPassword().trim(); // 配置DruidPlugin数据库连接池插件 DruidPlugin druidPlugin = new DruidPlugin(jdbcurl, user, password); // 配置ActiveRecord插件 ActiveRecordPlugin arp = new ActiveRecordPlugin(configname, druidPlugin); //false 是大写, true是小写, 不写是区分大小写, 看老项目情况配置 arp.setContainerFactory(new CaseInsensitiveContainerFactory(false)); //配置缓存类型 arp.setCache(new EhCache()); return Ret.create("druidPlugin", druidPlugin).set("arp", arp); } @Override public boolean start() { //TODO 我的测试代码放在 F:\workspace\moma_oa_test String url = PropKit.get(DictKeys.SITE_GETCONFIGSITE).trim() + ApiInterceptor.getMaskKit(); log.info("获取多站点配置信息网站地址: "+ url); String jsonStr = HttpKit.get(url); log.debug("获取的多站点配置: "+ jsonStr); JSONObject json = JSONObject.parseObject(jsonStr); if(json.getInteger(ApiInterceptor.ERRJSON_ERRCODE) != 0){ log.info("错误信息:" + json.getString(ApiInterceptor.ERRJSON_ERRMSG)); return false; } //解析json List<Site> sites = JSONArray.parseArray(json.getString(ApiInterceptor.ERRJSON_DATA), Site.class); load(sites); return true; } @Override public boolean stop() { //TODO 报告这些站点停了 return true; } }
想动态那就要接口调用了:
ConfigController:
import com.alibaba.fastjson.JSONObject; import com.jfinal.aop.Before; import com.jfinal.aop.Clear; import com.momathink.common.annotation.controller.Controller; import com.momathink.common.base.BaseController; import com.momathink.common.model.Site; import com.momathink.common.service.ActiveRecordPluginService; /***多站点 系统配置 操作API * @author dufuzhong@126.com * @date 2017年2月25日 下午1:59:07 */ @Before({ApiInterceptor.class }) @Controller(controllerKey = { "/api/config" }) public class ConfigController extends BaseController { /** 动态管理数据库和系统资源接口 */ public void operation(){ String data = getPara("data"); Site site = JSONObject.parseObject(data, Site.class); if(site.isStateOn()) ActiveRecordPluginService.me.add(site); else if(site.isStateOff()) ActiveRecordPluginService.me.del(site); renderJson(ApiInterceptor.errJson(site)); } }
接口 交接数据的 格式 规范:
import com.jfinal.aop.Interceptor; import com.jfinal.aop.Invocation; import com.jfinal.core.Controller; import com.jfinal.kit.HashKit; import com.jfinal.kit.JsonKit; import com.jfinal.kit.StrKit; /** * 接口 交接数据的 格式 规范 * 此拦截器仅做为示例展示,在本 demo 中 临时做一下 校验 */ public class ApiInterceptor implements Interceptor { /**接口 返回值 错误码 K */ public static final String ERRJSON_ERRCODE = "errcode"; /**接口 返回值 错误信息 K */ public static final String ERRJSON_ERRMSG = "errmsg"; /**接口 返回值 数据 K */ public static final String ERRJSON_DATA = "data"; /**接口 调用数据时 使用的 K */ public static final String CHECK_MASK = "mask"; public void intercept(Invocation inv) { Controller controller = inv.getController(); String mask = controller.getPara(CHECK_MASK); if(isMask(mask)){ try { inv.invoke(); } catch (Exception e) { inv.getController().renderJson(errJson(1, e.getMessage())); } }else inv.getController().renderJson(errJson(401, "mask验证失败")); } /**判断 密钥*/ private static boolean isMask(String mask){ if(StrKit.notBlank(mask)) return getMask().equals(mask.trim()); return false; } /***获取通行码 * 临时做一下 校验, 自行改造 */ public static String getMask(){ String mask = (new Date().getTime()+"").substring(0, 8) + "_dufuzhong@126.com"; //1.66665 分(min) return HashKit.md5(mask); } /***获取通行码 */ public static String getMaskKit(){ return "&"+ CHECK_MASK +"=" + getMask(); } //-------------- public static String errJson(Integer errcode, String errmsg) { return "{\"errcode\":" + errcode + ",\"errmsg\":\"" + errmsg + "\"}"; } public static String errJson(Object data) { return "{\"errcode\":0,\"errmsg\":\"OK\",\"data\":" + (JsonKit.toJson(data)) + "}"; } public static String errJson() { return "{\"errcode\":0,\"errmsg\":\"OK\"}"; } }
相关便利类;
Site
/** * Generated by JFinal. */ @SuppressWarnings("serial") public class Site extends BaseSite<Site> { public static final Site dao = new Site(); /**关机**/ public static final Integer STATE_off = 0; /**开机**/ public static final Integer STATE_on = 1; /**操作中**/ public static final Integer STATE_out = 2; public boolean isStateOn(){ return Site.STATE_on.equals(getState()); } public boolean isStateOff(){ return Site.STATE_off.equals(getState()); } }
/** * Generated by JFinal, do not modify this file. */ @SuppressWarnings("serial") public abstract class BaseSite<M extends BaseSite<M>> extends Model<M> implements IBean { public void setId(java.lang.Integer id) { set("id", id); } public java.lang.Integer getId() { return get("id"); } public void setConfigName(java.lang.String configName) { set("configName", configName); } public java.lang.String getConfigName() { return get("configName"); } public void setWebsite(java.lang.String website) { set("website", website); } public java.lang.String getWebsite() { return get("website"); } public void setJdbcUrl(java.lang.String jdbcUrl) { set("jdbcUrl", jdbcUrl); } public java.lang.String getJdbcUrl() { return get("jdbcUrl"); } public void setUser(java.lang.String user) { set("user", user); } public java.lang.String getUser() { return get("user"); } public void setPassword(java.lang.String password) { set("password", password); } public java.lang.String getPassword() { return get("password"); } public void setHostId(java.lang.Integer hostId) { set("hostId", hostId); } public java.lang.Integer getHostId() { return get("hostId"); } public void setState(java.lang.Integer state) { set("state", state); } public java.lang.Integer getState() { return get("state"); } public void setErrcode(java.lang.Integer errcode) { set("errcode", errcode); } public java.lang.Integer getErrcode() { return get("errcode"); } public void setErrmsg(java.lang.String errmsg) { set("errmsg", errmsg); } public java.lang.String getErrmsg() { return get("errmsg"); } }
到此基本完结了
最近有些小忙,小结后会分享 动态部署JFinal项目和升级项目, 也就是控制中心(自动运维)配合项目使用
最后打个广告有需求的可以联系下:
摩码创想(北京)科技有限公司 赵 睿 ZhaoRui
Tel:186 0067 6071 QQ:28747355 微信:zhaorui93
-------------------------------------------------------------------------------------------
“教育IT化”一站式全套解决方案 {1v1量身设计,满意付费 } 免费升级,终身维护!
学员档案 CRM、市场招生、渠道管理、销售管理、报名交费、排课教务、教学评价、课件管理、通知提醒、同步课表、课酬考勤、成绩管理、财务管理、数据统计、宿舍管理、库存教材、人事行政、学习报告、就业管理、在线支付、网上预约、在线购课、在线考试、网络课堂(移动端、微信端、数据批量导入、导出,打印、会员卡、积分、身份证) + 更多自由设计功能