计划等项目小结后再来分享项目得, 看到有不少问这个的,就先分享出一文章吧.
@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、市场招生、渠道管理、销售管理、报名交费、排课教务、教学评价、课件管理、通知提醒、同步课表、课酬考勤、成绩管理、财务管理、数据统计、宿舍管理、库存教材、人事行政、学习报告、就业管理、在线支付、网上预约、在线购课、在线考试、网络课堂(移动端、微信端、数据批量导入、导出,打印、会员卡、积分、身份证) + 更多自由设计功能