JFinal使用技巧-多数据源自动切换和动态管理

JFinal使用技巧-多数据源自动切换和动态管理

https://jfinal.com/share/236

自动切换:

Model的切换:

3.0  中 Model.getConfig() 的可见性由 private 改为 protected,
所以在你的 BaseModel 中重写这一个,不动其他代码 就搞定了Model自动切换数据源,

  1.     @Override
  2.     protected Config getConfig() {
  3.         
  4.         String configName = WebsiteInterceptor.getConfigName();
  5.         
  6.         if(configName == null)
  7.             configName = DbKit.MAIN_CONFIG_NAME;
  8.         
  9.         return DbKit.getConfig(configName);
  10.     }

Db的切换:

我用Ctrl + H 进行全局Db.替换为: Db.use(getConfigName()). 或者db().

PS:先搜索"Db.use(" 看系统中有这么用的没有,如果有先处理掉

我的各个Base层都有getConfigName()这个方法,
当然都是调的 WebsiteInterceptor.getConfigName();

各个BaseXxx层:

  1. /***
  2.      * @Title: 获取访问者的 ConfigName
  3.      * @return String    本次访问的ConfigName
  4.      */
  5.     public String getConfigName(){
  6.         return WebsiteInterceptor.getConfigName();
  7.     }
  8.     public DbPro db(){
  9.         return Db.use(getConfigName());
  10.     }

拦截器WebsiteInterceptor:

  1. import javax.servlet.http.HttpServletRequest;
  2.  
  3. import com.jfinal.aop.Interceptor;
  4. import com.jfinal.aop.Invocation;
  5. import com.jfinal.core.Controller;
  6. import com.jfinal.kit.PropKit;
  7. import com.jfinal.log.Log;
  8. import com.momathink.common.constants.DictKeys;
  9. import com.momathink.common.service.ActiveRecordPluginService;
  10.  
  11. /**
  12.  * @ClassName: 访问者的ConfigName管理
  13.  * @author dufuzhong@126.com
  14.  * @date 2016年10月3日 下午2:33:33
  15.  */
  16. public class WebsiteInterceptor implements Interceptor {
  17.     private static final Log log = Log.getLog(WebsiteInterceptor.class);
  18.     
  19.     private static final ThreadLocal<String> ME_CONFIGNAME = new ThreadLocal<String>();
  20.     
  21.     public static final String SERVER_NAME = "serverName";
  22.     public static final String CONFIG_NAME = "configName";
  23.     
  24.     /***
  25.      * @Title: 获取访问者的 ConfigName
  26.      * @return String    本次访问的ConfigName
  27.      */
  28.     public static String getConfigName(){
  29.         return ME_CONFIGNAME.get();
  30.     }
  31.     
  32.     /* nginx 配置 内容:
  33.      
  34.             location xxxx {
  35.                 proxy_set_header Host $host:80;
  36.                 proxy_set_header X-Forwarded-For $remote_addr;
  37.                 proxy_redirect  off;
  38.                 proxy_pass http://127.0.0.1:8080;
  39.                 # 配置多台tomcat 就 改  端口号等
  40.             }
  41.         
  42.      */
  43.     
  44.     @Override
  45.     public void intercept(Invocation inv) {
  46.         Controller controller = inv.getController();
  47.         HttpServletRequest request = controller.getRequest();
  48.         
  49.         String ipFromNginx = controller.getHeader("X-Forwarded-For");
  50.         String serverName  = request.getServerName();
  51.         /*
  52.          如果你的数据源是已知固定的  在启动JFinal的时候 用 serverName 做数据源的 configName 也就是说他们是相等的, 
  53.          到这里就可以完结了, 如果是动态的,需要看后的动态管理方式
  54.          * */
  55.         // String configName = serverName;  下面的判断也 改成 if(DbKit.getConfig(configName) != null){
  56.         String configName  = ActiveRecordPluginService.me.getConfigName(serverName);
  57.         
  58.         controller.setAttr(SERVER_NAME, serverName);
  59.         controller.setAttr(CONFIG_NAME, configName);
  60.         
  61.         log.debug("访问者:  域名=" + serverName + "  资源K=" + configName + "   IP=" + ipFromNginx);
  62.         
  63.         if(configName != null){
  64.             
  65.             ME_CONFIGNAME.set(configName);
  66.             
  67.             //异常必须在里面的拦截器进行捕捉处理
  68.             inv.invoke();
  69.             
  70.             ME_CONFIGNAME.remove();
  71.             
  72.         } else {
  73.             controller.renderError(403);
  74.         }
  75.     }
  76.  
  77. }

如果你的数据源是已知固定的  在启动JFinal的时候
 用 serverName 做数据源的 configName 也就是说他们是相等的,
 到这里就可以完结了, 如果是动态的,需要看后的动态管理方式

动态管理:

ActiveRecordPluginService管理控制

  1. import java.net.URLEncoder;
  2. import java.util.HashMap;
  3. import java.util.List;
  4. import java.util.Map;
  5.  
  6. import com.alibaba.fastjson.JSONArray;
  7. import com.alibaba.fastjson.JSONObject;
  8. import com.jfinal.kit.HttpKit;
  9. import com.jfinal.kit.JsonKit;
  10. import com.jfinal.kit.PropKit;
  11. import com.jfinal.kit.Ret;
  12. import com.jfinal.log.Log;
  13. import com.jfinal.plugin.IPlugin;
  14. import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
  15. import com.jfinal.plugin.activerecord.CaseInsensitiveContainerFactory;
  16. import com.jfinal.plugin.activerecord.DbKit;
  17. import com.jfinal.plugin.activerecord.cache.EhCache;
  18. import com.jfinal.plugin.druid.DruidPlugin;
  19. import com.momathink.api.ApiInterceptor;
  20. import com.momathink.common.constants.DictKeys;
  21. import com.momathink.common.model.Site;
  22.  
  23. /**(域名白名单) 
  24.  * <br>
  25.  * 多站点配置信息和多数据源 管理
  26.  * @author dufuzhong@126.com
  27.  * @date 2016年11月8日 下午19:16:23
  28.  */
  29. public class ActiveRecordPluginService implements IPlugin {
  30.     
  31.     private static final Log log = Log.getLog(ActiveRecordPluginService.class);
  32.     
  33.     public static final ActiveRecordPluginService me = new ActiveRecordPluginService();
  34.  
  35.     /***
  36.      * 存储 多站点信息, 域名为 键, 数据源configName 为 值
  37.      */
  38.     private static final Map<String, String> CONFIGNAME_S = new HashMap<String, String>();
  39.     static{//初始化值
  40.         CONFIGNAME_S.put("localhost", DbKit.MAIN_CONFIG_NAME);
  41.         CONFIGNAME_S.put("127.0.0.1", DbKit.MAIN_CONFIG_NAME);
  42.     }
  43.     
  44.     private ActiveRecordPluginService(){}
  45.     public static ActiveRecordPluginService me() {
  46.         return me;
  47.     }
  48.     
  49.     /***
  50.      * @Title: 获取某站点 数据源configName
  51.      * @param key 访问者域名
  52.      * @return String    该站点数据源configName
  53.      */
  54.     public String getConfigName(String key) {
  55.         return CONFIGNAME_S.get(key);
  56.     }
  57.     
  58.     /***
  59.      * 加载 多站点配置信息
  60.      * @return 
  61.      */
  62.     public synchronized boolean load(List<Site> sites){
  63.         log.info("加载 多站点配置信息");
  64.         for (Site site : sites)
  65.             add(site);
  66.         
  67.         //报告记录一下启动情况
  68.         
  69.         String url = PropKit.get(DictKeys.SITE_SETCONFIGSITE).trim() + ApiInterceptor.getMaskKit();
  70.         try {
  71.             String data = URLEncoder.encode(JsonKit.toJson(sites), "UTF-8");
  72.             log.info("报告多站点多数据源配置信息网站地址: "+ url);
  73.             String post = HttpKit.post(url, "&data=" + data);
  74.             log.info("报告完毕:" + post);
  75.         } catch (Exception e) {
  76.             log.info("报告异常,可能是没有启动运维服务器, 异常信息:" + e.getMessage());
  77.             return false;
  78.         }
  79.         return true;
  80.     }
  81.      
  82.     /***
  83.      * 动态 配置 数据库参数 和 加载系统资源
  84.      */
  85.     public synchronized void add(Site site) {
  86.         log.info("解析内容:" +  site);
  87.         //加 try catch 的原因是 不能因为某个 站点 的错误 配置信息, 影响到 其他的站点
  88.         try {
  89.             //配置 数据库参数 和 加载系统资源
  90.             Ret addDb = init(site);
  91.             
  92.             //数据库启动载入, 启动成功则连接成功, 否则会异常
  93.             boolean druidPlugin = ((DruidPlugin) addDb.get("druidPlugin")).start();
  94.             boolean arp = ((ActiveRecordPlugin) addDb.get("arp")).start();
  95.             
  96.             //连接池和管理器都要成功true 才能通过
  97.             if(!(druidPlugin) || !(arp)) throw new Exception("连接池和管理器");
  98.             
  99.             //网址域名
  100.             String website = site.getWebsite().trim();
  101.             
  102.             //该网站的系统资源 KEY 值
  103.             String configname = site.getConfigName().trim();
  104.             
  105.             //存储 该站点资源KEY信息
  106.             CONFIGNAME_S.put(website, configname);
  107.             
  108.             //成功返回码
  109.             site.keep("id");
  110.             site.set(ApiInterceptor.ERRJSON_ERRCODE, 0).set(ApiInterceptor.ERRJSON_ERRMSG, "OK");
  111.             
  112.         } catch (Exception e) {
  113.             //错误码表 后面再写... 先直接 看错误信息吧
  114.             site.set(ApiInterceptor.ERRJSON_ERRCODE, 500).set(ApiInterceptor.ERRJSON_ERRMSG, e.getMessage());
  115.             
  116.             log.info("配置 数据库参数 错误信息:" +  site);
  117.         }
  118.     }
  119.     
  120.     /***
  121.      * 移除 动态 配置 数据库参数 和 加载的系统资源
  122.      */
  123.     public synchronized void del(Site site) {
  124.         CONFIGNAME_S.remove(site.getWebsite());
  125.         DbKit.removeConfig(site.getConfigName());
  126.         //成功返回码
  127.         site.keep("id");
  128.         site.set(ApiInterceptor.ERRJSON_ERRCODE, 0).set(ApiInterceptor.ERRJSON_ERRMSG, "OK");
  129.     }
  130.  
  131.     /**配置数据库参数 和 加载系统资源
  132.      */
  133.     private Ret init(Site site) {
  134.  
  135.         //该网站的系统资源 KEY 值
  136.         String configname = site.getConfigName().trim();
  137.         
  138.         String jdbcurl = site.getJdbcUrl().trim();
  139.         String user = site.getUser().trim();
  140.         String password = site.getPassword().trim();
  141.         
  142.         // 配置DruidPlugin数据库连接池插件
  143.         DruidPlugin druidPlugin = new DruidPlugin(jdbcurl, user, password);
  144.         
  145.         // 配置ActiveRecord插件
  146.         ActiveRecordPlugin arp = new ActiveRecordPlugin(configname, druidPlugin);
  147.         
  148.         //false 是大写, true是小写, 不写是区分大小写, 看老项目情况配置
  149.         arp.setContainerFactory(new CaseInsensitiveContainerFactory(false));
  150.         
  151.         //配置缓存类型
  152.         arp.setCache(new EhCache());
  153.         
  154.         return Ret.create("druidPlugin", druidPlugin).set("arp", arp);
  155.     }
  156.     
  157.  
  158.     @Override
  159.     public boolean start() {
  160.         //TODO 我的测试代码放在    F:workspacemoma_oa_test
  161.         String url = PropKit.get(DictKeys.SITE_GETCONFIGSITE).trim() + ApiInterceptor.getMaskKit();
  162.         log.info("获取多站点配置信息网站地址: "+ url);
  163.         String jsonStr =  HttpKit.get(url);
  164.         log.debug("获取的多站点配置: "+ jsonStr);
  165.         JSONObject json = JSONObject.parseObject(jsonStr);
  166.         
  167.         if(json.getInteger(ApiInterceptor.ERRJSON_ERRCODE) != 0){
  168.             log.info("错误信息:" + json.getString(ApiInterceptor.ERRJSON_ERRMSG));
  169.             return false;
  170.         }
  171.         
  172.         //解析json
  173.         List<Site> sites = JSONArray.parseArray(json.getString(ApiInterceptor.ERRJSON_DATA), Site.class);
  174.         
  175.         load(sites);
  176.         
  177.         return true;
  178.     }
  179.  
  180.     @Override
  181.     public boolean stop() {
  182.         //TODO 报告这些站点停了
  183.         return true;
  184.     }
  185.  
  186. }

想动态那就要接口调用了:

ConfigController:

  1. import com.alibaba.fastjson.JSONObject;
  2. import com.jfinal.aop.Before;
  3. import com.jfinal.aop.Clear;
  4. import com.momathink.common.annotation.controller.Controller;
  5. import com.momathink.common.base.BaseController;
  6. import com.momathink.common.model.Site;
  7. import com.momathink.common.service.ActiveRecordPluginService;
  8.  
  9. /***多站点 系统配置 操作API
  10.  * @author dufuzhong@126.com
  11.  * @date 2017年2月25日 下午1:59:07
  12.  */
  13. @Before({ApiInterceptor.class })
  14. @Controller(controllerKey = { "/api/config" })
  15. public class ConfigController extends BaseController {
  16.  
  17.     /** 动态管理数据库和系统资源接口
  18.      */
  19.     public void operation(){
  20.         String data = getPara("data");
  21.         Site site = JSONObject.parseObject(data, Site.class);
  22.         
  23.         if(site.isStateOn())
  24.             ActiveRecordPluginService.me.add(site);
  25.         
  26.         else if(site.isStateOff())
  27.             ActiveRecordPluginService.me.del(site);
  28.         
  29.         renderJson(ApiInterceptor.errJson(site));
  30.     }
  31.     
  32.   
  33.     
  34.     
  35. }

接口 交接数据的 格式 规范:

  1. import com.jfinal.aop.Interceptor;
  2. import com.jfinal.aop.Invocation;
  3. import com.jfinal.core.Controller;
  4. import com.jfinal.kit.HashKit;
  5. import com.jfinal.kit.JsonKit;
  6. import com.jfinal.kit.StrKit;
  7. /**
  8.  * 接口 交接数据的 格式 规范
  9.  * 此拦截器仅做为示例展示,在本 demo 中 临时做一下 校验
  10.  */
  11. public class ApiInterceptor implements Interceptor {
  12.     
  13.     /**接口 返回值  错误码 K */
  14.     public static final String ERRJSON_ERRCODE = "errcode";
  15.     /**接口 返回值  错误信息 K */
  16.     public static final String ERRJSON_ERRMSG  = "errmsg";
  17.     /**接口 返回值  数据 K */
  18.     public static final String ERRJSON_DATA  = "data";
  19.     /**接口 调用数据时 使用的 K */
  20.     public static final String CHECK_MASK  = "mask";
  21.     
  22.     public void intercept(Invocation inv) {
  23.         Controller controller = inv.getController();
  24.         String mask = controller.getPara(CHECK_MASK);
  25.         if(isMask(mask)){
  26.             
  27.             try {
  28.                 inv.invoke();
  29.             } catch (Exception e) {
  30.                 inv.getController().renderJson(errJson(1, e.getMessage()));
  31.             }
  32.             
  33.         }else inv.getController().renderJson(errJson(401, "mask验证失败"));
  34.     }
  35.     
  36.     /**判断 密钥*/
  37.     private static boolean isMask(String mask){
  38.         if(StrKit.notBlank(mask))
  39.             return getMask().equals(mask.trim());
  40.         return false;
  41.     }
  42.     
  43.     
  44.     /***获取通行码
  45.      * 临时做一下 校验, 自行改造
  46.      */
  47.     public static String getMask(){
  48.         String mask = (new Date().getTime()+"").substring(0, 8) + "_dufuzhong@126.com";
  49.         //1.66665    分(min)
  50.         return HashKit.md5(mask);
  51.     }
  52.     
  53.     /***获取通行码
  54.      */
  55.     public static String getMaskKit(){
  56.         return "&"+ CHECK_MASK +"=" + getMask();
  57.     }
  58.     
  59.     
  60.     //--------------
  61.     
  62.     public static String errJson(Integer errcode, String errmsg) {
  63.         return "{"errcode":" + errcode + ","errmsg":"" + errmsg + ""}";
  64.     }
  65.     
  66.     public static String errJson(Object data) {
  67.         return "{"errcode":0,"errmsg":"OK","data":" + (JsonKit.toJson(data)) + "}";
  68.     }
  69.     
  70.     public static String errJson() {
  71.         return "{"errcode":0,"errmsg":"OK"}";
  72.     }
  73. }

相关便利类;

  1. Site
  1. /**
  2.  * Generated by JFinal.
  3.  */
  4. @SuppressWarnings("serial")
  5. public class Site extends BaseSite<Site> {
  6.     public static final Site dao = new Site();
  7.     
  8.     /**关机**/
  9.     public static final Integer STATE_off = 0;
  10.     /**开机**/
  11.     public static final Integer STATE_on = 1;
  12.     /**操作中**/
  13.     public static final Integer STATE_out = 2;
  14.     
  15.     
  16.     public boolean isStateOn(){
  17.         return Site.STATE_on.equals(getState());
  18.     }
  19.     
  20.     public boolean isStateOff(){
  21.         return Site.STATE_off.equals(getState());
  22.     }
  23.     
  24. }
  1. /**
  2.  * Generated by JFinal, do not modify this file.
  3.  */
  4. @SuppressWarnings("serial")
  5. public abstract class BaseSite<extends BaseSite<M>> extends Model<M> implements IBean {
  6.  
  7.     public void setId(java.lang.Integer id) {
  8.         set("id", id);
  9.     }
  10.  
  11.     public java.lang.Integer getId() {
  12.         return get("id");
  13.     }
  14.  
  15.     public void setConfigName(java.lang.String configName) {
  16.         set("configName", configName);
  17.     }
  18.  
  19.     public java.lang.String getConfigName() {
  20.         return get("configName");
  21.     }
  22.  
  23.     public void setWebsite(java.lang.String website) {
  24.         set("website", website);
  25.     }
  26.  
  27.     public java.lang.String getWebsite() {
  28.         return get("website");
  29.     }
  30.  
  31.     public void setJdbcUrl(java.lang.String jdbcUrl) {
  32.         set("jdbcUrl", jdbcUrl);
  33.     }
  34.  
  35.     public java.lang.String getJdbcUrl() {
  36.         return get("jdbcUrl");
  37.     }
  38.  
  39.     public void setUser(java.lang.String user) {
  40.         set("user", user);
  41.     }
  42.  
  43.     public java.lang.String getUser() {
  44.         return get("user");
  45.     }
  46.  
  47.     public void setPassword(java.lang.String password) {
  48.         set("password", password);
  49.     }
  50.  
  51.     public java.lang.String getPassword() {
  52.         return get("password");
  53.     }
  54.  
  55.     public void setHostId(java.lang.Integer hostId) {
  56.         set("hostId", hostId);
  57.     }
  58.  
  59.     public java.lang.Integer getHostId() {
  60.         return get("hostId");
  61.     }
  62.  
  63.     public void setState(java.lang.Integer state) {
  64.         set("state", state);
  65.     }
  66.  
  67.     public java.lang.Integer getState() {
  68.         return get("state");
  69.     }
  70.  
  71.     public void setErrcode(java.lang.Integer errcode) {
  72.         set("errcode", errcode);
  73.     }
  74.  
  75.     public java.lang.Integer getErrcode() {
  76.         return get("errcode");
  77.     }
  78.  
  79.     public void setErrmsg(java.lang.String errmsg) {
  80.         set("errmsg", errmsg);
  81.     }
  82.  
  83.     public java.lang.String getErrmsg() {
  84.         return get("errmsg");
  85.     }
  86.  
  87. }

到此基本完结了

原文地址:https://www.cnblogs.com/zkwarrior/p/14964471.html