In the last chapter, we added the game’s main screen and the login function. As a result of the distance on the long interval, may be some content to think of some later forgotten. At the same time, the logic is not complicated, so the description is rough.

Now with the addition of modules, the whole architecture is showing some problems. In this chapter, we will perform a large-scale refactoring of the entire system.

For example, in order to quickly develop the RMS module, we used direct access to the database, for the RMS module itself, there is no problem.

However, in the game module, for frequently accessed data or interfaces that do not change often, we hope to adopt the way of caching to cache the data, reduce the back-end pressure, while speeding up the response speed, so as to improve the experience.

EhCache was used in RMS module as memory cache. But now that the GAME module is added, the memory cache cannot be shared between the two processes. Therefore, we introduced Redis to store the cached data uniformly in Redis. Here we first use spring-data-redis for caching. Caching method returns by marking annotations on methods of a Service. Such a coarse-grained cache currently meets most requirements. Later, when necessary, we will manually operate Redis for fine-grained caching.

In addition to cache modification, I found that some enumerated values, such as: race, class, faction, etc., are currently defined in the form of static classes, enumerated classes, in each module, so whenever I change, I need to change several places at once. Therefore, I added a data dictionary table to uniformly configure this kind of data into the database, and since it is infrequently modified, each module can read it directly into the cache. The UML class diagram for the data dictionary is shown below.

In this case, ALL I need is a static class that enumerates the parent configuration, which will only be added later and generally not changed. The code is as follows:

package com.idlewow.datadict.model; import java.io.Serializable; Public enum DataType implements Serializable {Occupy("10100", "territory "), Faction("10110"," Faction "), Race("10200", "Race "), Job (" 10250 ", "professional"), MobType (" 10300 ", "monster types"), MobClass (" 10310 ", "monster types"); private String code; private String value; DataType(String code, String value) { this.code = code; this.value = value; } public String getCode() { return code; } public String getValue() { return value; }}Copy the code

DataType.java

Spring cache

In the Spring-Context package, there is an annotation class for caching that you can use directly. Just mark annotations on methods that need caching. There are mainly @cacheable, @cacheevict, and @cacheput.

Redis: mapMob:#{id}; redis: mapMob:#{id}

@Cacheable(value = "mapMob", key = "#id", unless = "#result == null")
public CommonResult find(String id) {
    return super.find(id);
}    
Copy the code

Example 2: The following comment indicates that after this method is successfully executed, all keys in the cache dataDict: are cleared

@CacheEvict(value = "dataDict", allEntries = true)
public CommonResult update(DataDict dataDict) {
    return super.update(dataDict);
}
Copy the code

Example 3: Update the cache with key levelExp:#{id} to the result returned by the method after successful execution

@CachePut(value = "levelExp", key = "#levelExp.getId()")
public CommonResult update(LevelExp levelExp) {
    return super.update(levelExp);
}
Copy the code

1. Cache transformation

Since caching is done on hessian’s methods, we add the following dependencies to the pom.xml of the hessian module:

<! --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis > < version > 2.2.0. RELEASE < / version > < / dependency > < the dependency > < groupId > redis. Clients < / groupId > < artifactId > jedis < / artifactId > < version > 3.1.0 < / version > < / dependency >Copy the code

pom.xml

Here, we need to configure a bean called cacheManager to store the annotated cache in Redis by referring to the spring-data-Redis package.

We have been using XML to configure components, and this cacheManager can also be configured using XML. But in practice, I want to uniformly configure redis key as IDleWOW: XXX :… , we have studied for a long time and failed to find the configuration method in XML form, so we use Java code for configuration here.

Add the package com.idlewow to the hessian module and create the CacheConfig class as follows:

package com.idlewow.config; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import redis.clients.jedis.JedisPoolConfig; import java.time.Duration; @EnableCaching @Configuration public class CacheConfig extends CachingConfigurerSupport { @Bean public JedisPoolConfig jedisPoolConfig() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(200); jedisPoolConfig.setMaxIdle(50); jedisPoolConfig.setMinIdle(20); jedisPoolConfig.setMaxWaitMillis(5000); jedisPoolConfig.setTestOnBorrow(true); jedisPoolConfig.setTestOnReturn(false); return jedisPoolConfig; } @Bean public JedisConnectionFactory jedisConnectionFactory() { JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig()); return jedisConnectionFactory; } @Bean public CacheManager cacheManager() { RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofHours(1)) .disableCachingNullValues() .computePrefixWith(cacheName -> "idlewow:" + cacheName + ":"); RedisCacheManager redisCacheManager = RedisCacheManager.builder(jedisConnectionFactory()) .cacheDefaults(configuration) .build(); return redisCacheManager; }}Copy the code

CacheConfig

If the result is NULL, the cache will not be cached. The key prefix is idlewow:. If you are interested, you can explore whether you can configure the key prefix in XML. Note that the spring-data-redis 2.x version is used, which is quite different from the 1.x version.

Once the dependencies are added, we need to mark the methods of the service. The implementation class of the service, under the Core module.

For example, if the MapMobServiceImpl methods update, delete, and find are successfully executed, we need to update the cache. Since we do not cache NULL values, there is no need to update the cache after the add execution. Find (id) : BaseServiceImpl: super.find(id) : super.find(id); The code is as follows:

package com.idlewow.mob.service.impl; import com.idlewow.common.BaseServiceImpl; import com.idlewow.common.model.CommonResult; import com.idlewow.mob.manager.MapMobManager; import com.idlewow.mob.model.MapMob; import com.idlewow.mob.service.MapMobService; import com.idlewow.query.model.MapMobQueryParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.util.List; @Service("mapMobService") public class MapMobServiceImpl extends BaseServiceImpl<MapMob, MapMobQueryParam> implements MapMobService { @Autowired MapMobManager mapMobManager; @override @cacheput (value = "mapMob", key = "#mapMob.getId()") public CommonResult update(MapMob mapMob) { return super.update(mapMob); @override @cacheevict (value = "mapMob", key = "#id") public CommonResult delete(String id) { return super.delete(id); } @override @cacheable (value = "mapMob", key = "#id") public CommonResult find(String id) { return super.find(id); } @override @cacheable (value = "mapMobList", key = "#mapId", unless = "#reuslt==null") public List<MapMob> listByMapId(String mapId) { try { return mapMobManager.listByMapId(mapId);  } catch (Exception ex) { logger.error(ex.getMessage(), ex); return null; }}}Copy the code

MapMobServiceImpl

OK, the hessian module cache has been modified. You can try to call it, redis should already be able to write data.

Also: HERE I’ve added a listByMapId method that the game module will call later. There is no consistent return of the CommonResult type. Because IN the process of writing the actual code, I found that every time to call the interface to make a judgment is too tedious, internal call generally do not need so much trouble. Generally, exceptions can be captured and processed when interdepartmental or intercompany interfaces are interconnected or fault tolerance requirements are high. Therefore, all subsequent internal interfaces return the required data type directly.

2. Corresponding transformation of RMS system

Hessian has been changed to redis cache. If the RMS module does not update the redis cache, the game module will not update the redis cache when it calls hessian.

Therefore, we modified the RMS module to read and write data by accessing the Hessian service, so that when the hessian method is called, the cache is triggered instead of directly accessing the database.

Delete EhCache, database-related code, configuration, and dependencies. Add a reference to hessian in the POM and, like the Game module, configure hessian-client.xml and introduce it in applicationContext.xml.

In the code, we replace BaseManager with BaseService in CrudController and make corresponding changes elsewhere. The diagram below:

package com.idlewow.rms.controller; import com.idlewow.common.model.BaseModel; import com.idlewow.common.model.CommonResult; import com.idlewow.common.model.PageList; import com.idlewow.common.model.QueryParam; import com.idlewow.common.service.BaseService; import com.idlewow.util.validation.ValidateGroup; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; public abstract class CrudController<T extends BaseModel, Q extends QueryParam> extends BaseController { private final String path = this.getClass().getAnnotation(RequestMapping.class).value()[0]; @Autowired BaseService<T, Q> baseService; @Autowired HttpServletRequest request; @RequestMapping("/list") public Object list() { return this.path + "/list"; } @ResponseBody @RequestMapping(value = "/list", method = RequestMethod.POST) public Object list(@RequestParam(value = "page", defaultValue = "1") int pageIndex, @RequestParam(value = "limit", defaultValue = "10") int pageSize, Q q) { try { q.setPage(pageIndex, pageSize); CommonResult commonResult = baseService.list(q); if (commonResult.isSuccess()) { PageList<T> pageList = (PageList<T>) commonResult.getData(); return this.parseTable(pageList); } else { request.setAttribute("errorMessage", commonResult.getMessage()); return "/error"; } } catch (Exception ex) { logger.error(ex.getMessage(), ex); request.setAttribute("errorMessage", ex.getMessage()); return "/error"; } } @RequestMapping("/add") public Object add() { return this.path + "/add"; } @ResponseBody @RequestMapping(value = "/add", method = RequestMethod.POST) public Object add(@RequestBody T t) { try { CommonResult commonResult = this.validate(t, ValidateGroup.Create.class); if (! commonResult.isSuccess()) return commonResult; t.setCreateUser(this.currentUserName()); commonResult = baseService.insert(t); return commonResult; } catch (Exception ex) { logger.error(ex.getMessage(), ex); return CommonResult.fail(ex.getMessage()); } } @RequestMapping(value = "/edit/{id}", method = RequestMethod.GET) public Object edit(@PathVariable String id, Model model) { try { CommonResult commonResult = baseService.find(id); if (commonResult.isSuccess()) { T t = (T) commonResult.getData(); model.addAttribute(t); return this.path + "/edit"; } else { request.setAttribute("errorMessage", commonResult.getMessage()); return "/error"; } } catch (Exception ex) { logger.error(ex.getMessage(), ex); request.setAttribute("errorMessage", ex.getMessage()); return "/error"; } } @ResponseBody @RequestMapping(value = "/edit/{id}", method = RequestMethod.POST) public Object edit(@PathVariable String id, @RequestBody T t) { try { if (! Id. Equals (t.getid ())) {return commonresult.fail ("id not consistent "); } CommonResult commonResult = this.validate(t, ValidateGroup.Update.class); if (! commonResult.isSuccess()) return commonResult; t.setUpdateUser(this.currentUserName()); commonResult = baseService.update(t); return commonResult; } catch (Exception ex) { logger.error(ex.getMessage(), ex); return CommonResult.fail(ex.getMessage()); } } @ResponseBody @RequestMapping(value = "/delete/{id}", method = RequestMethod.POST) public Object delete(@PathVariable String id) { try { baseService.delete(id); return CommonResult.success(); } catch (Exception ex) { logger.error(ex.getMessage(), ex); return CommonResult.fail(ex.getMessage()); }}}Copy the code

CrudController.java

Also, because the data dictionary has been added. The RMS module needs to add the corresponding Contoller and page. I will not repeat them here. Now that we have a data dictionary, EnumUtil can be abolished for dead enumerations. Read the data dictionary configuration directly from hessian into the cache.

In com. Idlewow. RMS. Support. Add DataDictUtil class under the util package, the code is as follows:

package com.idlewow.rms.support.util; import com.idlewow.common.model.CommonResult; import com.idlewow.datadict.model.DataDict; import com.idlewow.datadict.model.DataType; import com.idlewow.datadict.service.DataDictService; import com.idlewow.query.model.DataDictQueryParam; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; @Component public class DataDictUtil implements Serializable { private static final Logger logger = LogManager.getLogger(DataDictUtil.class); @Autowired DataDictService dataDictService; public static Map<String, Map<String, String>> ConfigMap = new HashMap<>(); public void initialize() { DataDictQueryParam dataDictQueryParam = new DataDictQueryParam(); CommonResult commonResult = dataDictService.list(dataDictQueryParam); if (commonResult.isSuccess()) { List<DataDict> dataDictList = (List<DataDict>) commonResult.getData(); for (DataDict dataDict : dataDictList) { if (ConfigMap.containsKey(dataDict.getParentCode())) { ConfigMap.get(dataDict.getParentCode()).put(dataDict.getCode(), dataDict.getValue()); } else { Map map = new HashMap(); map.put(dataDict.getCode(), dataDict.getValue()); ConfigMap.put(dataDict.getParentCode(), map); }}} else {logger.error(" Cache loading failed!" ); } } public static Map<String, String> occupy() { return ConfigMap.get(DataType.Occupy.getCode()); } public static Map<String, String> job() { return ConfigMap.get(DataType.Job.getCode()); } public static Map<String, String> faction() { return ConfigMap.get(DataType.Faction.getCode()); } public static Map<String, String> mobClass() { return ConfigMap.get(DataType.MobClass.getCode()); } public static Map<String, String> mobType() { return ConfigMap.get(DataType.MobType.getCode()); }}Copy the code

DataDictUtil.java

In StartUpListener, initialize the cache:

@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Logger. info(" Cache initialization..." ); dataDictUtil.initialize(); Logger. info(" Cache initialization completed..." ); }Copy the code

Back-end caching is now available, as is front-end dead enumerations. You can use localStorage for caching. The code is as follows:

/* Data dictionary cache */ var _cache = {version: new Date().getTime(), configMap: null}; / * * / read cache function loadCache () {if (_cache. Configmap = = null | | (new Date (). The getTime () - _cache. Version) > 1000 * 60 * 30) { var localConfigMap = localStorage.getItem("configmap"); if (localConfigMap) { _cache.configmap = JSON.parse(localConfigMap); } else {/* Read data dictionary cache */ $. Ajax ({url: '/manage/data_dict/configMap', type: 'post', success: function (data) { _cache.configmap = data; localStorage.setItem("configmap", JSON.stringify(_cache.configmap)); }, error: function () { alert('ajax error'); }}); }}} Key * / var / * data dictionary DataType = {" Occupy ":" 10100 ", / / territory belonging "extension" : "10110", / / camp "Race" : "10200", "Job" : / / Race "10250", // Class" MobType": "10300", // monster type "MobClass": "10310" // monster type}; DataDict.prototype = { occupy: function (value) { return _cache.configmap[DataType.Occupy][value]; }, job: function (value) { return _cache.configmap[DataType.Job][value]; }, faction: function (value) { return _cache.configmap[DataType.Faction][value]; }, mobClass: function (value) { return _cache.configmap[DataType.MobClass][value]; }, mobType: function (value) { return _cache.configmap[DataType.MobType][value]; }}; loadCache();Copy the code

Helper.js

Note that jQuery’s Ajax request is used here and you must reference jQuery before you reference it.

summary

There are some omissions. I’ll fill them in next week.

Download source address: 545c.com/file/149603…

Project Exchange Group: 329989095