zcxv

Легендарный
Проверенный
Победитель в номинации 2015
Сообщения
562
Розыгрыши
0
Репутация
812
Реакции
1 360
Баллы
1 703
Пару месяцев назад подключал к проекту и первое, что его использовало - html cache, что собственно, и постится.
Никаких секретов и нечто крутого тут нет. Любой человек, который может читать документацию - реализует это все очень легко. К тому же ehcache интуитивно понятный.

Caches
Код:
package ru.catssoftware.gameserver.cache;

import lombok.Getter;
import net.sf.ehcache.CacheManager;

/**
* @author PointerRage
*
*/
public class Caches {
   @Getter(lazy=true) private final static CacheManager manager = CacheManager.create();
}

Reloadable
Код:
package fork2.loaders;

/**
* @author PointerRage
*
*/
public interface Reloadable {
   void reload();
}

HtmCache
Код:
package ru.catssoftware.gameserver.cache;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.concurrent.TimeUnit;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration.Strategy;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
import ru.catssoftware.gameserver.Config;
import ru.catssoftware.gameserver.ThreadPoolManager;
import ru.catssoftware.gameserver.model.actor.instance.L2PcInstance;
import ru.catssoftware.gameserver.model.quest.Quest;
import ru.catssoftware.gameserver.network.component.Lang;
import ru.catssoftware.gameserver.util.Util;
import fork2.loaders.Reloadable;
import fork2.startup.Singleton;
import fork2.startup.Startup;

/**
* @author PointerRage
*
*/
@Startup("IdFactory")
@Singleton
@Slf4j
public class HtmCache implements Reloadable {
   @Getter(lazy=true)
   private final static HtmCache instance = new HtmCache();
  
   private final static Charset charser = Charset.forName("UTF-8");

   private final static String OLD_QUEST_FOLDER = "data/scripts/";
   private final static String QUEST_FOLDER = "data/html/quests/";

   private HtmCache() {
     for(int i = 0; i < Lang.values().length; i++) {
       CacheConfiguration conf = new CacheConfiguration(Lang.values()[ i ].name().concat("-html"), 1500)
         .eternal(false)
         .timeToLiveSeconds(TimeUnit.HOURS.toSeconds(1))
         .timeToIdleSeconds(TimeUnit.MINUTES.toSeconds(30))
         .persistence(new PersistenceConfiguration().strategy(Strategy.NONE))
         .diskExpiryThreadIntervalSeconds(0)
         .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU);
       Caches.getManager().addCache(new Cache(conf));
     }
    
     ThreadPoolManager.getInstance().executeGeneral(() -> reload());
   }
  
   private Cache getCache(Lang lang) throws IllegalStateException, ClassCastException {
     return Caches.getManager().getCache(lang.name().concat("-html"));
   }
  
   private String compactHtml(String html) {
     html = html.replace("  ", " ").replace("\r","");
     html = html.replace("< ", "<").replace(" >", ">");
     html = html.replace("\n\n", "\n").replace(">\n", ">").replace("\n<", "<");
     html = html.replace("<!--if","\n<!--if");
     return html.trim();
   }
  
   private String getHtm(Lang lang, String file) {
     Cache cache;
     try {
       cache = getCache(lang);
     } catch(IllegalStateException | ClassCastException e) {
       log.error("Cache for {} not found!", lang.name());
       return String.format("cache for %s not found!", lang.name());
     }
    
     Element element;
     try {
       element = cache.get(file);
     } catch(IllegalStateException | CacheException e) {
       return null;
     }
    
     if(element == null || element.getObjectValue() == null)
       return null;
    
     return element.getObjectValue().toString();
   }
  
   private boolean loadElement(Lang lang, String file) {
     String tag = lang.getPathName();
     int inx = file.lastIndexOf('/');
     File f = new File(file.substring(0, inx).concat(tag).concat(file.substring(inx)));
     boolean shared = false;
     if(!f.exists() || !f.isFile()) {
       f = new File(file);
       shared = true;
       if(!f.exists() || !f.isFile())
         return false;
     }
    
     byte[] bytes;
     try {
       bytes = Files.readAllBytes(f.toPath());
     } catch (IOException e) {
       log.error("Failed to load data: {}", f);
       return false;
     }
     String data = compactHtml(new String(bytes, charser));
    
     if(!shared) {
       Cache cache = getCache(lang);
       cache.put(new Element(file, data));
     } else {
       for(Lang l : Lang.values()) {
         Cache cache = getCache(l);
         cache.put(new Element(file, data));
       }
     }
     return true;
   }

   public String getHtm(String file, L2PcInstance player)
   {
     return getHtm(file, player, true);
   }

   public String getHtm(String file, L2PcInstance player, boolean force) {
     if (player == null) {
       log.error("Request HTML 'getHtml(file, player)', with param player == null!", new RuntimeException());
       return "player is null, error!";
     }
    
     if(player.isGM())
       player.sendMessage("File: {}", file);
    
     return getHtm(file, player.getLang(), force);
   }

   public String getHtm(String file, Lang lang, boolean force) {
     file = file.replace("//", "/");
     String html = getHtm(lang, file);

     if (html == null) {
       if(loadElement(lang, file))
         return getHtm(lang, file);
      
       if (lang.isRU()) {
         if(loadElement(Lang.EN, file))
           return getHtm(Lang.EN, file);
       } else {
         if(loadElement(Lang.RU, file))
           return getHtm(Lang.RU, file);
       }
     }
    
     if (html == null && !force) {
       html = "<html>";
       html += lang == Lang.RU ? "HTML <font color=LEVEL>'" + file + "'</font> не существует." : "HTML <font color=LEVEL>'" + file + "'</font> not found.";
       html += "</html>";
     }

     return html;
   }

   public String getHtmForce(String file, L2PcInstance player) {
     return getHtm(file, player);
   }

   public String getQuestHtm(String fileName, Quest quest, L2PcInstance player) {
     String questFolder = null;
     if(quest.getScriptFile() != null)
       questFolder = Util.getRelativePath(Config.DATAPACK_ROOT, new File(quest.getScriptFile()).getParentFile());
     else {
       questFolder = QUEST_FOLDER.concat(quest.getName()).concat("/");
      
       String data = getHtm(questFolder, player, true);
       if(data == null) {
         questFolder = OLD_QUEST_FOLDER;
         if(quest.getQuestIntId() < 0 || quest.getQuestIntId() > 2000)
           questFolder += "custom/";
         else
           questFolder += "quests/";
         questFolder += quest.getName();
       }
     }

     return getHtm(questFolder.concat("/").concat(fileName), player, false);
   }
  
   public boolean htmExists(String file) {
     File f;
     for(Lang lang : Lang.values()) {
       String tag = lang.isRU() ? "/ru" : "/en";
       int inx = file.lastIndexOf('/');
       f = new File(file.substring(0, inx).concat(tag).concat(file.substring(inx)));
       if(!f.exists() || !f.isFile())
         continue;
       return true;
     }
    
     f = new File(file);
     if(!f.exists() || !f.isFile())
       return false;
     return true;
   }
  
   @Override
   public void reload() {
     for(Lang lang : Lang.values()) {
       String name = lang.name().concat("-html");
       Cache cache = Caches.getManager().getCache(name);
       log.info("Cache {} with {} elements cleared!", name, cache.getSize());
       cache.removeAll();
     }
   }
  
   public String[] getInfo() {
     String[] nfo = new String[Lang.values().length];
     for(int i = 0; i < Lang.values().length; i++) {
       String name = Lang.values()[ i ].name().concat("-html");
       Cache cache = Caches.getManager().getCache(name);
       nfo[ i ] = String.format("Cache %s have %d elements", name, cache.getSize());
     }
     return nfo;
   }
}

Lang
Код:
package ru.catssoftware.gameserver.network.component;

import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public enum Lang {
   RU("ru.properties", new ConcurrentHashMap<String, String>()) {
     @Override
     public String getPathName() {
       return "/ru".intern();
     }
    
     @Override
     String getErrorMessage() {
       return "Сообщения не существует".intern();
     }
   },
   EN("en.properties", new ConcurrentHashMap<String, String>()) {
     @Override
     public String getPathName() {
       return "/en".intern();
     }
    
     @Override
     String getErrorMessage() {
       return "Message not found.".intern();
     }
   };


   private final static Logger log = LoggerFactory.getLogger(Lang.class);

   private final String filename;
   private final ConcurrentHashMap<String, String> messages;

   Lang(String filename, ConcurrentHashMap<String, String> messages)
   {
     this.filename = filename;
     this.messages = messages;
   }
  
   public abstract String getPathName();
   abstract String getErrorMessage();

   public String getMessage(String name) {
     String message = messages.get(name);

     if (message == null) {
       message = getErrorMessage();
       log.error("Custom message '{}' not found for language: {}", name, name());
     }

     return message;
   }

   public String getMessage(String name, Object... values) {
     String message = getMessage(name);
    
     if (values == null || values.length == 0)
       return message;

     return String.format(message, values);
   }

   public void addMessage(String name, String value) {
     messages.put(name, value);
   }

   public String getFilenameMessages() {
     return filename;
   }

   public boolean isRU() {
     return this == RU;
   }

   public void clearCustomMessage() {
     messages.clear();
   }
}

На счет собственных ощущений: какого-либо снижения скорости работы, я не увидел (при том, что у меня идет еще оверлей шаблонизатора); можно в полной мере оффхипить (если данных много и они тяжелые); персистент сейв на диск кеша (с помощью чего данные не теряются при перезагрузках), но она показалась мне немного странной, т.к. данные как-то странно синхронизируются с диском; различные виды кешей; выгрузки данных и куча всего. Одним словом - удобно.

FAQ
Q: шта такое @Singletone/@Startup?
A: это мои аннотации от системы старта сервера; можно просто их удалить.

Q: шта такое @Getter, @Slf4j и другое из lombok пакага?7
A: фреймворк кодогенерации, очень рекомендую; бережет кучу времени ( ), так же есть поддержка для IDEA, Eclipse, NetBeans.

P.S: 2admin отключите работу тегов внутри тега [ code ], а то не хорошо получается :)
 
Последнее редактирование:
Назад
Сверху Снизу