java怎么制作放置游戏_从零开始实现放置游戏(八)——实现后台管理系统(6)代码重构...

本文介绍了如何使用泛型模板对一个Java游戏后台管理系统进行重构,以提高代码复用性和可维护性。通过创建BaseMapper和BaseManager抽象接口和类,实现了数据访问层的通用操作。同时,对控制器层进行重构,抽象出CrudController和ExcelController,简化了增删查改和Excel导入功能的实现。重构后,业务代码更加简洁,提高了开发效率。
摘要由CSDN通过智能技术生成

前几张,我们主要实现了升级经验、人物等级属性、地图、地图怪物,这四种配置的增删查改以及Excel导入功能。我们主要以地图怪物为例,因此在文章末尾提供的源代码中只实现了地图怪物这部分的逻辑功能。

如果你照猫画虎,把4种配置功能的逻辑全部实现的话,就会发现,增删查改的代码基本相同,除了SQL语句和模型对象不同,其他地方变化不大。

本章我们利用泛型模板,对整个系统就行重构。在重构结束后,你就会发现写代码简直就是特喵的艺术!

后端重构

idlewow-core

我们从最底层开始,首先重构位于core模块中的数据访问层。目前看来,基本上所有的模型对象,都应包含增删查改、批量添加、列表查询这些基本方法。那我们把这些方法抽象到一个单独的Mapper和Manager里。

新建com.idlewow.common包,再该包下新建接口类BaseMapper:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.idlewow.common;importcom.idlewow.common.model.QueryParam;importjava.util.List;public interface BaseMapper{/*** 添加记录

*@paramt*/

intinsert(T t);/*** 批量添加记录

*@paramlist

*@return

*/

int batchInsert(Listlist);/*** 更新记录

*@paramt*/

intupdate(T t);/*** 删除记录

*@paramid*/

intdelete(String id);/*** 根据id查询

*@paramid

*@return

*/T find(String id);/*** 根据条件查询总数

*@paramqueryParam

*@return

*/

intcount(QueryParam queryParam);/*** 根据条件查询列表

*@paramqueryParam

*@return

*/Listlist(QueryParam queryParam);

}

BaseMapper.java

再在该包下,新建一个抽象类BaseManager,代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.idlewow.common;importcom.idlewow.common.model.PageList;importcom.idlewow.common.model.QueryParam;importorg.springframework.beans.factory.annotation.Autowired;importjava.util.List;public abstract class BaseManager{

@Autowiredpublic BaseMapperbaseMapper;public voidinsert(T t) {int effected =baseMapper.insert(t);if (effected == 0) {throw new RuntimeException("sql effected 0 rows");

}

}public void batchInsert(Listlist) {int splitSize = 100;int index = 0;int total =list.size();while (index <=total) {int end = index +splitSize;if (end >total) {

end=total;

}

List sublist =list.subList(index, end);int effected =baseMapper.batchInsert(sublist);if (effected == 0) {throw new RuntimeException("sql effected 0 rows");

}

index+=splitSize;

}

}public voidupdate(T t) {int effected =baseMapper.update(t);if (effected == 0) {throw new RuntimeException("sql effected 0 rows");

}

}public voiddelete(String id) {int effected =baseMapper.delete(id);if (effected == 0) {throw new RuntimeException("sql effected 0 rows");

}

}publicT find(String id) {

T t=(T)baseMapper.find(id);returnt;

}public PageListlist(QueryParam queryParam) {

PageList pageList = new PageList<>();int count =baseMapper.count(queryParam);

List list =baseMapper.list(queryParam);

pageList.setTotalCount(count);

pageList.setData(list);

pageList.setPageParam(queryParam.getPageParam());returnpageList;

}

}

BaseManager.java

还是以地图怪物为例,我们重构MapMobMapper和MapMobManager,只要让他们继承BaseMapper和BaseManager即可,代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.idlewow.mob.mapper;importcom.idlewow.common.BaseMapper;importcom.idlewow.mob.model.MapMob;public interface MapMobMapper extends BaseMapper{

}

MapMobMapper.java

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.idlewow.mob.manager;importcom.idlewow.common.BaseManager;importcom.idlewow.mob.model.MapMob;importorg.springframework.stereotype.Component;

@Componentpublic class MapMobManager extends BaseManager{

}

MapMobManager.java

重构后的Mapper和Manager直接继承基类的增删查改方法,无需在各个业务中一遍又一遍的书写重复代码。

idlewow-rms

在rms模块中,主要对controller中的重复代码进行重构。在前几章,我们抽象出过一个BaseController,在里面实现了一些最基础的方法。这里,我们再抽象出一个CrudController来实现数据的增删查该;在CrudController的基础上,再抽象出一个ExcelController来实现Excel的批量导入。

在com.idlewow.rms.controlelr包下新建抽象类CrudContoller,代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.idlewow.rms.controller;importcom.idlewow.common.BaseManager;importcom.idlewow.common.model.BaseModel;importcom.idlewow.common.model.CommonResult;importcom.idlewow.common.model.PageList;importcom.idlewow.common.model.QueryParam;importcom.idlewow.util.validation.ValidateGroup;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.ui.Model;importorg.springframework.web.bind.annotation.PathVariable;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestMethod;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.ResponseBody;public abstract class CrudController extendsBaseController {private final String path = this.getClass().getAnnotation(RequestMapping.class).value()[0];

@Autowired

BaseManagerbaseManager;

@RequestMapping("/list")publicObject 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") intpageSize, Q q) {

q.setPage(pageIndex, pageSize);

PageList pageList =baseManager.list(q);return this.parseTable(pageList);

}

@RequestMapping("/add")publicObject add() {return this.path + "/add";

}

@ResponseBody

@RequestMapping(value= "/add", method =RequestMethod.POST)publicObject add(@RequestBody T t) {try{

CommonResult commonResult= this.validate(t, ValidateGroup.Create.class);if (!commonResult.isSuccess())returncommonResult;

t.setCreateUser(this.currentUserName());

baseManager.insert(t);returnCommonResult.success();

}catch(Exception ex) {

logger.error(ex.getMessage(), ex);returnCommonResult.fail();

}

}

@RequestMapping(value= "/edit/{id}", method =RequestMethod.GET)publicObject edit(@PathVariable String id, Model model) {

T t=baseManager.find(id);

model.addAttribute(t);return this.path + "/edit";

}

@ResponseBody

@RequestMapping(value= "/edit/{id}", method =RequestMethod.POST)publicObject edit(@PathVariable String id, @RequestBody T t) {try{if (!id.equals(t.getId())) {return CommonResult.fail("id不一致");

}

CommonResult commonResult= this.validate(t, ValidateGroup.Update.class);if (!commonResult.isSuccess())returncommonResult;

t.setUpdateUser(this.currentUserName());

baseManager.update(t);returnCommonResult.success();

}catch(Exception ex) {

logger.error(ex.getMessage(), ex);returnCommonResult.fail();

}

}

@ResponseBody

@RequestMapping(value= "/delete/{id}", method =RequestMethod.POST)publicObject delete(@PathVariable String id) {try{

baseManager.delete(id);returnCommonResult.success();

}catch(Exception ex) {

logger.error(ex.getMessage(), ex);returnCommonResult.fail();

}

}

}

CrudController.java

这个CrudController起到了一个模板的作用,可以说非常的精髓。首先,利用泛型约束,解决了不同业务数据模型、查询参数不同的问题。然后,通过反射获取不同业务controller的Url映射,解决了不同业务跳转页面路径不同的问题。具体业务的controller直接继承此类,无需再写任何代码,即可实现增删查改。

下面我们再实现Excel导入功能的模板类,在该包内新建一个抽象类ExcelController,代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.idlewow.rms.controller;importcom.idlewow.common.model.BaseModel;importcom.idlewow.common.model.CommonResult;importcom.idlewow.common.model.QueryParam;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RequestMethod;importorg.springframework.web.bind.annotation.ResponseBody;importorg.springframework.web.multipart.MultipartFile;importorg.springframework.web.multipart.MultipartHttpServletRequest;importorg.springframework.web.multipart.commons.CommonsMultipartResolver;importjavax.servlet.ServletContext;importjavax.servlet.http.HttpServletRequest;importjava.io.File;importjava.util.Iterator;importjava.util.List;public abstract class ExcelController extends CrudController{

@ResponseBody

@RequestMapping(value= "/importExcel", method =RequestMethod.POST)publicObject importExcel(HttpServletRequest request) {try{

ServletContext servletContext=request.getServletContext();

String uploadPath= servletContext.getRealPath("/upload");

File dir= newFile(uploadPath);if (!dir.exists()) {

dir.mkdir();

}

CommonsMultipartResolver multipartResolver= newCommonsMultipartResolver(servletContext);if(multipartResolver.isMultipart(request)) {

MultipartHttpServletRequest multiRequest=(MultipartHttpServletRequest) request;

Iterator iter =multiRequest.getFileNames();while(iter.hasNext()) {

MultipartFile file=multiRequest.getFile(iter.next());if (file.getSize() > 0) {

String fileName=file.getOriginalFilename();

String extension= fileName.substring(fileName.lastIndexOf("."));if (!extension.toLowerCase().equals(".xls") && !extension.toLowerCase().equals(".xlsx")) {throw new Exception("不支持的文档格式!请上传.xls或.xlsx格式的文档!");

}

String destFileName= fileName + "_" + System.currentTimeMillis() +extension;

File destFile= newFile(uploadPath, destFileName);

file.transferTo(destFile);

List dataList = this.loadExcelData(destFile.getPath());this.saveExcelData(dataList);if (destFile.exists() && !destFile.delete()) {

logger.error("删除临时文件失败!" +destFile.getAbsolutePath());

}

}

}

}returnCommonResult.success();

}catch(Exception ex) {

logger.error(ex.getMessage(), ex);returnCommonResult.fail();

}

}protected abstract List loadExcelData(String excelPath) throwsException;protected void saveExcelData(ListdataList) {this.baseManager.batchInsert(dataList);

}

}

ExcelController.java

在这个类中,我们将Excel导入功能分解成3个方法。importExcel,对应前端点击事件,保存上传的临时文件;saveExcelData,保存解析出的数据,即调用mapper的批量添加方法;这两个方法都是通用的,直接在ExcelController中实现即可。只有loadExcelData,解析Excel数据这个方法,不同业务的实现不同,我们把它定义成抽象方法,等待各个业务自己实现。

好了,基类已经定义好了,我们让MapMobController继承ExcelController即可,代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.idlewow.rms.controller;importcom.idlewow.common.constant.DataDict;importcom.idlewow.map.model.WowMap;importcom.idlewow.mob.model.MapMob;importcom.idlewow.query.model.MapMobQueryParam;importcom.idlewow.util.poi.PoiUtil;importorg.apache.poi.ss.usermodel.Sheet;importorg.apache.poi.xssf.usermodel.XSSFRow;importorg.apache.poi.xssf.usermodel.XSSFWorkbook;importorg.ehcache.Cache;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.RequestMapping;importjava.io.FileInputStream;importjava.util.ArrayList;importjava.util.List;

@Controller

@RequestMapping("/manage/map_mob")public class MapMobController extends ExcelController{

@AutowiredprotectedCache mapCache;protected List loadExcelData(String excelPath) throwsException {

FileInputStream fileInputStream= newFileInputStream(excelPath);

XSSFWorkbook workbook= newXSSFWorkbook(fileInputStream);

Sheet sheet= workbook.getSheet("怪物");

List mapMobList = new ArrayList<>();//处理当前页,循环读取每一行

String createUser = this.currentUserName();for (int rowNum = 2; rowNum <= sheet.getLastRowNum(); rowNum++) {

XSSFRow row=(XSSFRow) sheet.getRow(rowNum);

String mapName= PoiUtil.getCellValue(row.getCell(1));

String mobName= PoiUtil.getCellValue(row.getCell(2));

String faction= PoiUtil.getCellValue(row.getCell(3));

String mobClass= PoiUtil.getCellValue(row.getCell(4));

String mobType= PoiUtil.getCellValue(row.getCell(5));

Integer level= Integer.valueOf(PoiUtil.getCellValue(row.getCell(6)));

Integer hp= Integer.valueOf(PoiUtil.getCellValue(row.getCell(7)));

Integer damage= Integer.valueOf(PoiUtil.getCellValue(row.getCell(8)));

Integer amour= Integer.valueOf(PoiUtil.getCellValue(row.getCell(9)));

WowMap wowMap=(WowMap)mapCache.get(mapName);

MapMob mapMob= newMapMob();

mapMob.setMapId(wowMap.getId());

mapMob.setMapName(wowMap.getName());

mapMob.setName(mobName);

mapMob.setFaction(DataDict.Faction.getByDesc(faction).getCode());

mapMob.setMobClass(DataDict.MobClass.getByDesc(mobClass).getCode());

mapMob.setMobType(DataDict.MobType.getByDesc(mobType).getCode());

mapMob.setLevel(level);

mapMob.setHp(hp);

mapMob.setDamage(damage);

mapMob.setAmour(amour);

mapMob.setCreateUser(createUser);

mapMobList.add(mapMob);

}

fileInputStream.close();returnmapMobList;

}

}

MapMobController.java

现在业务Controller里,只有一个独立实现的loadExcelData方法,再也不用重复书写增删查改了。(注意:这个类里多了个缓存对象mapCache,是我用来缓存地图数据的。pom中添加了对应包,具体可在源码中查看。用法比较简单,可搜EhCache。)

前端重构

除了后端代码冗余外,其实很容易发现,前端的代码重复部分也很多,尤其是写ajax请求的部分,其实每次变化的只有url地址,或者请求参数等。我们直接在js里定义一个类来实现增删查改等这些ajax请求的通用部分。

在/webapp/js/helper.js中,定义一个类CRUD,代码如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

…………

…………var CRUD = function() {

};

CRUD.prototype={

list:function(cols, url) {var table =layui.table;

table.render({

elem:'#datatable', url: url

, method:'post', cellMinWidth:80, cols: cols

, page: {

layout: ['limit', 'count', 'prev', 'page', 'next', 'skip'] //自定义分页布局

, limits: [10, 20, 30, 40, 50]

, groups:3 //只显示 1 个连续页码

, first: '首页', last:'尾页'}

});

},

upload:function(url, extension) {

layui.upload.render({

elem:'#btnSelectFile',

url: url,

accept:'file',

exts: extension,

auto:false,

bindAction:'#btnImport',

done:function(result) {if (result.code === 1) {

layer.alert(result.message, {icon:6},function() {

layui.layer.closeAll();

layui.table.reload('datatable');

});

}else{

layer.alert(result.message, {icon:5});

}

}

});

},

search:function(data) {var table =layui.table;

table.reload('datatable', {

where: data,

page: {

curr:1}

});

},

add:function(url) {var form =layui.form;

form.on('submit(add)',function(data) {

$.ajax({

url: url,

type:'post',

contentType:"application/json; charset=utf-8",

data: JSON.stringify(data.field),

success:function(result) {if (result.code === 1) {

layer.alert(result.message, {icon:6},function() {

xadmin.close();

xadmin.father_reload();

});

}else{

layer.alert(result.message, {icon:5});

}

},

error:function() {

layer.alert("请求失败", {icon: 5});

}

});

});

},

edit:function(url) {var form =layui.form;

form.on('submit(edit)',function(data) {

$.ajax({

url: url+ '/' +data.field.id,

type:'post',

contentType:"application/json; charset=utf-8",

data: JSON.stringify(data.field),

success:function(result) {if (result.code === 1) {

layer.alert(result.message, {icon:6},function() {

xadmin.close();

xadmin.father_reload();

});

}else{

layer.alert(result.message, {icon:5});

}

},

error:function() {

layer.alert("请求失败", {icon: 5});

}

});

});

},

remove:function(obj, url) {

layer.confirm('确认要删除吗?', function() {

$.ajax({

url: url,

type:'post',

success:function(result) {if (result.code === 1) {

$(obj).parents("tr").remove();

layer.msg('删除成功', {icon: 1, time: 1000});

}else{

layer.alert("删除失败", {icon: 5});

}

},

error:function() {

layer.alert("请求失败", {icon: 5});

}

});

});

}

};

window.crud= newCRUD();

…………

…………

Helper.js

然后,在/webapp/js/wow/map_mob中,修改add.js, edit.js 和 list.js 如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

layui.use(['form', 'layer'],function() {var form =layui.form;

form.verify({});

crud.add('/manage/map_mob/add');

});

add.js

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

layui.use(['form', 'layer'],function() {var form =layui.form;

form.render();

form.verify({});

crud.edit('/manage/map_mob/edit/');

});

edit.js

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

layui.use(['upload', 'table', 'form'], function() {var cols =[[

{field:'id', width: 50, title: 'id'}

, {field:'name', title: '怪物名称'}

, {field:'mapName', title: '地图名称'}

, {

field:'faction', title: '阵营', templet: function(d) {returnenumUtil.faction(d.faction);

}

}

, {

field:'mobClass', title: '怪物种类', templet: function(d) {returnenumUtil.mobClass(d.mobClass);

}

}

, {

field:'mobType', title: '怪物类型', templet: function(d) {returnenumUtil.mobType(d.mobType);

}

}

, {field:'level', title: '等级'}

, {field:'hp', title: '生命值'}

, {field:'damage', title: '伤害'}

, {field:'amour', title: '护甲'}

, {

title:'操作', width: 150, templet: function(d) {return '编辑' +

'删除';

}

}

]];

crud.list(cols,'/manage/map_mob/list');

crud.upload('/manage/map_mob/importExcel', 'xls|xlsx');

});functionsearch() {var data ={

name: $('input[name="name"]').val(),

levelStart: $('input[name="levelStart"]').val(),

levelEnd: $('input[name="levelEnd"]').val(),

faction: $('select[name="faction"]').val(),

mobClass: $('select[name="mobClass"]').val(),

mobType: $('select[name="mobType"]').val()

};

crud.search(data);

}functionreset(){

$('#queryForm').reset();

}functionremove(obj, id) {

crud.remove(obj,'/manage/map_mob/delete/' +id);

}

list.js

可以看到,重构后的js,和后端一样,简洁多了。

运行效果

这里,由于改动了core模块,需要先对项目编译打包。再运行rms模块,即可正常启动项目。启动后,效果和之前一样,只是代码变得简洁多了,这里就不再截图了。

小结

本章终于把冗余的代码进行了重构,整个代码瞬间提升了几层逼格,变得干净多了。

说明:我的代码风格就是不套用设计模式,在不确定最终效果时,不做过多提前设计,先实现了再说,实现了再慢慢重构,还有就是几乎不注释。

最近因为有其他的事情,所以停更了一周。写到这里其实发现这一大章题目似乎叫RMS系统的初步实现更好。

写代码和写文章其实差别还有点大。自己写代码的时候,想到哪写到哪,各个模块并行随缘开发。但写文章就必须按一定顺序来,否则容易让人困惑。所以文章里的代码都是单独拉一个分支重新整理的。

项目交流群:329989095

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值