@TOCcss
做为技术人员的你,你可能遇到频繁的小项目不断的在建立(包括生产或技术语言),基础功能代码类似度达到90%,系统的基础接口、流程、参数等几近类似。每次新建项目就算你在熟悉,你也得花很大部分的时间(从数据库层到服务层到应用层到基础配置,我相信你在这里起码要花差很少半天的时间),不过你仍是不断的建立相似项目、不断的添加数据库操做层、不断的服务接口和实现层,最后还要为外部提供API接口,其实当你项目作多了你就知道你一直在干重复的工做,在吸食没有养分的垃圾实物。你把大量的时间浪费在基础项目框架搭建、基础代码的编写。html
你花了不少的时间仅仅是在作以下的工做:java
你须要新建符合本身的项目,添加Spring工具、数据库工具、JSon处理、Redis处理、日志处理等,每次须要根据本身的需求去添加剧复的maven依赖;mysql
咱们在后台常用MyBtis或JPA去作数据访问层,你须要编写对应的接口,还须要编写接口对应的SQL语句和映射关系,你须要对你设计的全部的table逐个一字不差的编写以便能顺利调试经过DAO接口,甚至你要添加不少个经过任意字段的组合均可以查询的通用接口,甚是吃力、费劲还不必定准确。git
要实现与数据库的数据面向对象交互,咱们必须提供与表相对应的PO对象,也就是咱们系统中必须用到的entity,不一样的系统虽然对应的model不太同样,但也就是model的名称和字段不同而已,不过相同的是有一个名字、有n个不一样名称的字段!为何不能从数据中将表转换为实体,这样不就大大减小了代码量?github
DAO是面向数据库层的,可是业务通常是变化的,因此咱们通常系统中咱们要抽离一层service接口出来面向系统业务实现事务操做,因此为了通常状况下针对业务层咱们要实现业务接口层、业务接口实现层的全部代码,业务层在经过DAO层来实现数据库的CURD操做,其实你会发现这层的业务基础的逻辑几乎与DAO层彻底类似(仅仅是个别的面向系统业务的接口须要个性化增长)。web
为了能提供对外接口,在MVC模式中咱们还必需要提供对外的Controller层,也就是咱们所说的API层,咱们须要将对应的业务暴露给外部或第三方平台,要求规范外部请求参数、请求方式、请求接口地址等,因此不得已咱们还必须实现一层业务控制层代码,经过调用业务服务层代码实现系统的业务功能。redis
因此的应用启动都必须经过启动配置进行启动,能够包括开发环境配置、生产环境配置、测试环境配置等,其实这一类的配置也几近是相同的,能够统一块儿来配置,个性化的配置才须要用户本身添加。spring
日志是系统必不可少的组件,咱们的系统必需要配置对应的日志信息,包括保存目录、存储方式、日志级别等。不过这一部分基本上咱们能够约定,差别性的才须要用户去更新,因此这部分的工做也能够统筹起来,使用代码生成。sql
项目开发过程当中咱们须要借助不少工具来保证系统的稳定运行,包括分布式日志、SQL性能监控、跨域访问、JSON转换、文本转换、日期转换、加解密、文件操做、HTTP调用等等等,不过你会发现没新建一个项目你都会火烧眉毛的将你封装的、感受很好用的工具通通都打包进去,由于你知道这个工具是很经常使用也基本上用的到,因此你每次都会拷贝它,我就想问,你每次这么作是否是感受好累,导出查找位置、拷贝粘贴、修改包名!(固然,最好的方法不是拷贝进去二十封装层本身的一个jar包库到系统中)。
若是你发现这个多东西都是可复制的,只须要做为模板,而后按照模板修改就能够新建成另外一个项目,不过你会发现即便你拷贝成另一个项目你也会面对以下问题:
被复制的项目通常包含了大量的与该系统业务相关的代码,因此你拿过去以后你须要删除全部与业务相关的代码;
由于业务不一样致使数据库设计不一样所带来的问题就是基本上全部重要的代码都要删除或重构,包括系统实体模型、数据mapper层、业务service层、service实现层、控制层。你会发现你基本上还不如重建一个新项目还来得干脆!
开发环境中,不是你改一个名字就能够处处运行,由于不少时候你修改的并不完全,改完以后一直到其余地方或环境发现是错误、红点,不熟悉的开发人员根本没法解决对应的红色问题。
既然以上两种方案有如此多的问题,既然代码工做重复性如此之大,为什么你依然执拗于重建重加剧改?那倒不如将重复的工做集成到一个项目中,之后建立项目的时候只须要专一于写好本身的SQL脚本,而后经过web网页或swagger输入本身的项目信息一键生成本身的项目,一鼓作气,何乐而不为?这也是本人的初衷,也算是为公司为各位网友节省大量的开发时间和人力成本。
对技术人员来讲,技术都不是问题,缺的就是想法,基本上没有实现不了的事,剩下的就是时间的问题了,通过2-3天的开发,基本上成型,实现了自动生成SpringBoot后台代码的初衷。
我将实现了代码生成的服务代码成了安装包,以下所示:
启动后以下所示(确保运行机器安装JDK而且80端口未被占用且按安装了mysql):
启动后远程机器监听的80端口,咱们直接经过浏览器访问对应机器ip便可:
http://192.168.8.18进入登陆页面
经过用户名admin,密码:123456进入系统便可(注意session在30分钟内过时从新登陆)
正如我说的,有了框架生成服务咱们只须要专一于本身的数据库设计便可,这里个人业务是在音视频这块的应用,我设计了2个表,数据库脚本以下所示:
-- ------------------------------------------------------ -- 建立并使用数据库 -- ------------------------------------------------------ set charset utf8; create database if not exists mclz character set UTF8; use mclz; -- -- 客户端连接信息表 -- create table if not exists t_connection ( clientId bigint(20) NOT NULL, /* 客户端标识 */ ip varchar(16) default NULL, /* srs访问的地址 */ vhost varchar(64) default NULL, /* 虚拟主机 */ app varchar(64) default NULL, /* 应用名称 */ tcUrl varchar(256) default NULL, /* 访问地址 */ pageUrl varchar(256) default NULL, /* 访问页面 */ reserver1 varchar(128) default NULL, /* 保留字段 */ reserver2 varchar(128) default NULL, /* 保留字段 */ primary key(clientId) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='client connection information'; -- -- 视频发布信息表 -- create table if not exists t_publish ( clientId bigint(20) NOT NULL, /* 客户端标识 */ ip varchar(16) default NULL, /* srs访问的地址 */ vhost varchar(64) default NULL, /* 虚拟主机 */ app varchar(64) default NULL, /* 应用名称 */ tcUrl varchar(256) default NULL, /* 访问地址 */ stream varchar(64) default NULL, /* 流名称 */ param varchar(128) default NULL, /* 携带参数 */ reserver1 varchar(128) default NULL, /* 保留字段 */ reserver2 varchar(128) default NULL, /* 保留字段 */ primary key(clientId) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='client publish stream'; -- -- 视频播放信息表 -- create table if not exists t_play ( clientId bigint(20) NOT NULL, /* 客户端标识 */ ip varchar(16) default NULL, /* srs访问的地址 */ vhost varchar(64) default NULL, /* 虚拟主机 */ app varchar(64) default NULL, /* 应用名称 */ stream varchar(64) default NULL, /* 流名称 */ param varchar(128) default NULL, /* 携带参数 */ reserver1 varchar(128) default NULL, /* 保留字段 */ reserver2 varchar(128) default NULL, /* 保留字段 */ primary key(clientId) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='client play stream';
个人业务中简单的就三个表,客户端链接表、视频发布表、视频播放表,设计完成个人业务以后咱们就能够经过上传该脚本生成本身的web后台系统,该系统包括了全部操做这3个表的DAO层、mybatis的XML配置、service层、service实现层、controller层以及如下静态网页。
经过上面的步骤咱们登录到框架生成系统中,以下所示
这里的项目建立界面比较简洁,我算是偷个懒,有空的时候在为你们优化一下界面。须要注意些的都已经注明:
个人sql脚本中建立的数据库名称为"mclz",因此我填入的项目信息以下所示:
项目包名:com.easystudy #该项做为全部包的基础包名应用到项目中 应用名称:hello #项目名称也是应用名称对应项目Context名 应用端口:6666 #项目对应的服务端口,http访问需明确指定 数据库地址:127.0.0.1 #部署机器必须安装mysql用户sql执行转化 数据库端口:3306 #服务暂时仅支持mysql数据库操做 数据库名称:mclz #设计的SQL中建立并使用的数据库名称 数据库用户:root #运行脚本的mysql链接用户名 数据库密码:root #运行脚本的mysql链接密码 数据库脚本:经过选择sql脚本文件上传后返回的sql临时文件名
点击“提交并建立项目”按钮新建项目,成功后自动下载新建完成后的项目!
咱们直接下载项目并导入到eclipse中便可,最后直接运行
根据咱们建立的项目名为hello,项目监听端口6666,咱们直接浏览器访问:
http://localhost:6666/hello/
能够看到后台框架系统已经生成,接口也已经生成提供(后面一块儿看看),你只须要实现你的UI界面开发便可!
数据库性能监控页面:
http://localhost:6666/hello/druid
生成好后台项目以后,咱们一块儿来看看生成的项目的目录结构,以下所示:
能够看到生成的可运行系统基本囊括了后台SpringBoot后台应用框架的全部的代码,包括:
部分代码以下:
package com.easystudy.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.PathVariable; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import com.easystudy.error.ErrorCode; import com.easystudy.error.ReturnValue; import com.easystudy.error.IOException; import com.easystudy.model.Connection; import com.easystudy.service.ConnectionService; /** * @文件名称: ConnectionController.java * @功能描述: 控制层对外接口 * @版权信息: www.easystudy.com * @技术交流: 961179337(QQ群) * @编写做者: lixx2048@163.com * @联系方式: 941415509(QQ) * @开发日期: 2020年10月19日 * @历史版本: V1.0 * @备注信息: */ @Slf4j @RestController @RequestMapping("/tconnection") @Api(tags = "Connection管理接口文档", value = "提供Connection管理相关接口") public class ConnectionController { @Autowired private ConnectionService connectionService; @PostMapping("/add") @ApiOperation(value="添加Connection信息", notes="添加Connection信息") @ApiImplicitParams({ @ApiImplicitParam(paramType = "body", dataType = "Connection", name = "connection", value = "Connection信息", required = true) }) public ReturnValue<String> add(@RequestBody() Connection connection){ try { connectionService.add(connection); return new ReturnValue<String>(); } catch (IOException e) { log.error("添加信息异常:{}", e.getMessage()); e.printStackTrace(); } return new ReturnValue<String>(ErrorCode.ERROR_SERVER_ERROR, "添加信息异常!"); } @DeleteMapping("/delete/{clientId}") @ApiOperation(value="删除Connection信息", notes="经过主键删除Connection信息") @ApiImplicitParams({ @ApiImplicitParam(paramType = "path", dataType = "Long", name = "clientId", value = "主键标识", required = true)}) public ReturnValue<String> delete(@PathVariable(name="clientId", required=true) Long clientId){ try { Connection temp = new Connection(); temp.setClientId(clientId); connectionService.delete(temp); return new ReturnValue<String>(); } catch (IOException e) { log.error("删除信息异常:{}", e.getMessage()); e.printStackTrace(); } return new ReturnValue<String>(ErrorCode.ERROR_SERVER_ERROR, "删除信息异常!"); } @PutMapping("/update") @ApiOperation(value="更新Connection信息", notes="更新Connection信息") @ApiImplicitParams({ @ApiImplicitParam(paramType = "body", dataType = "Connection", name = "connection", value = "Connection信息", required = true) }) public ReturnValue<String> update(@RequestBody Connection connection) { try { connectionService.update(connection); return new ReturnValue<String>(); } catch (IOException e) { log.error("更新信息异常:{}", e.getMessage()); e.printStackTrace(); } return new ReturnValue<String>(ErrorCode.ERROR_SERVER_ERROR, "删除信息异常!"); } @GetMapping("/findById/{clientId}") @ApiOperation(value="经过主键查找Connection信息", notes="经过主键查找Connection信息") @ApiImplicitParams({ @ApiImplicitParam(paramType = "path", dataType = "Long", name = "clientId", value = "主键标识", required = true)}) public ReturnValue<Connection> findById(@PathVariable(name="clientId", required=true) Long clientId){ try { Connection connection = connectionService.findById(clientId); if(null == connection) { return new ReturnValue<Connection>(ErrorCode.ERROR_NOT_FOUND); } return new ReturnValue<Connection>(connection); } catch (IOException e) { log.error("查询信息异常:{}", e.getMessage()); e.printStackTrace(); } return new ReturnValue<Connection>(ErrorCode.ERROR_SERVER_ERROR, "查询信息异常!"); } @GetMapping("/findMaxByAttributes") @ApiOperation(value="经过属性查找Connection总数", notes="经过属性查找Connection总数") @ApiImplicitParams({ @ApiImplicitParam(paramType = "query", dataType = "String", name = "ip", value = "ip", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "vhost", value = "vhost", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "app", value = "app", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "tcUrl", value = "tcUrl", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "pageUrl", value = "pageUrl", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "reserver1", value = "reserver1", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "reserver2", value = "reserver2", required = false) }) public ReturnValue<Long> findMaxByAttributes( @RequestParam(name="ip",required=false) String ip, @RequestParam(name="vhost",required=false) String vhost, @RequestParam(name="app",required=false) String app, @RequestParam(name="tcUrl",required=false) String tcUrl, @RequestParam(name="pageUrl",required=false) String pageUrl, @RequestParam(name="reserver1",required=false) String reserver1, @RequestParam(name="reserver2",required=false) String reserver2 ) { try { Long count = connectionService.findMaxByAttributes(ip,vhost,app,tcUrl,pageUrl,reserver1,reserver2); return new ReturnValue<Long>(count); } catch (IOException e) { log.error("查询信息异常:{}", e.getMessage()); e.printStackTrace(); } return new ReturnValue<Long>(ErrorCode.ERROR_SERVER_ERROR, "查询信息异常!"); } @GetMapping("/findByAttributes") @ApiOperation(value="查找Connection信息列表", notes="查找Connection信息列表") @ApiImplicitParams({ @ApiImplicitParam(paramType = "query", dataType = "Long", name = "pageIndex", value = "页索引", required = true), @ApiImplicitParam(paramType = "query", dataType = "Long", name = "pageSize", value = "页大小", required = true), @ApiImplicitParam(paramType = "query", dataType = "String", name = "orderProp", value = "排序字段", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "order", value = "排序方式(asc、desc)", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "ip", value = "ip", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "vhost", value = "vhost", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "app", value = "app", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "tcUrl", value = "tcUrl", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "pageUrl", value = "pageUrl", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "reserver1", value = "reserver1", required = false), @ApiImplicitParam(paramType = "query", dataType = "String", name = "reserver2", value = "reserver2", required = false) }) public ReturnValue<List<Connection>> findByAttributes( @RequestParam(name="pageIndex",required=true) Long pageIndex, @RequestParam(name="pageSize",required=true) Long pageSize, @RequestParam(name="orderProp",required=false) String orderProp, @RequestParam(name="order",required=false) String order, @RequestParam(name="ip",required=false) String ip, @RequestParam(name="vhost",required=false) String vhost, @RequestParam(name="app",required=false) String app, @RequestParam(name="tcUrl",required=false) String tcUrl, @RequestParam(name="pageUrl",required=false) String pageUrl, @RequestParam(name="reserver1",required=false) String reserver1, @RequestParam(name="reserver2",required=false) String reserver2 ) { try { List<Connection> list = connectionService.findByAttributes(pageIndex,pageSize,orderProp,order,ip,vhost,app,tcUrl,pageUrl,reserver1,reserver2); return new ReturnValue<List<Connection>>(list); } catch (IOException e) { log.error("经过属性查询信息异常:{}", e.getMessage()); e.printStackTrace(); } return new ReturnValue<List<Connection>>(ErrorCode.ERROR_SERVER_ERROR, "经过属性查询信息异常!"); } }
部分代码实例:
package com.easystudy.service; import com.easystudy.model.Connection; import com.easystudy.error.IOException; import java.util.List; /** * @文件名称: ConnectionService.java * @功能描述: 业务服务层接口 * @版权信息: www.easystudy.com * @技术交流: 961179337(QQ群) * @编写做者: lixx2048@163.com * @联系方式: 941415509(QQ) * @开发日期: 2020年10月19日 * @历史版本: V1.0 * @备注信息: */ public interface ConnectionService extends BaseService<Connection> { Long findMaxByAttributes(String ip,String vhost,String app,String tcUrl,String pageUrl,String reserver1,String reserver2) throws IOException; List<Connection> findByAttributes(Long pageIndex,Long pageSize,String orderProp,String order,String ip,String vhost,String app,String tcUrl,String pageUrl,String reserver1,String reserver2) throws IOException; }
部分代码示例:
package com.easystudy.service.impl; import com.easystudy.model.Connection; import com.easystudy.error.IOException; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.easystudy.mapper.ConnectionMapper; import com.easystudy.service.ConnectionService; /** * @文件名称: ConnectionServiceImpl.java * @功能描述: 业务服务实现类 * @版权信息: www.easystudy.com * @技术交流: 961179337(QQ群) * @编写做者: lixx2048@163.com * @联系方式: 941415509(QQ) * @开发日期: 2020年10月19日 * @历史版本: V1.0 * @备注信息: */ @Service public class ConnectionServiceImpl implements ConnectionService { @Autowired private ConnectionMapper mapper; @Override @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, isolation = Isolation.REPEATABLE_READ) public void add(Connection o) throws IOException { mapper.insertSelective(o); } @Override public void delete(Connection o) throws IOException { mapper.deleteByPrimaryKey(o.getClientId()); } @Override public void update(Connection o) throws IOException { mapper.updateByPrimaryKeySelective(o); } @Override public Connection findById(Object id) throws IOException { return mapper.selectByPrimaryKey((Long)id); } @Override public List<Connection> findByAttributes(Long pageIndex,Long pageSize,String orderProp,String order,String ip,String vhost,String app,String tcUrl,String pageUrl,String reserver1,String reserver2) throws IOException { return mapper.selectByAttributes(pageIndex,pageSize,orderProp,order,ip,vhost,app,tcUrl,pageUrl,reserver1,reserver2); } @Override public Long findMaxByAttributes(String ip,String vhost,String app,String tcUrl,String pageUrl,String reserver1,String reserver2) throws IOException { return mapper.selectMaxByAttributes(ip,vhost,app,tcUrl,pageUrl,reserver1,reserver2); } }
部分实现代码:
package com.easystudy.mapper; import com.easystudy.model.Connection; import java.util.List; import org.apache.ibatis.annotations.Param; /** * @文件名称: ConnectionMapper.java * @功能描述: Dao层数据操做接口 * @版权信息: www.easystudy.com * @技术交流: 961179337(QQ群) * @编写做者: lixx2048@163.com * @联系方式: 941415509(QQ) * @开发日期: 2020年10月19日 * @历史版本: V1.0 * @备注信息: */ public interface ConnectionMapper { int deleteByPrimaryKey(Long clientId); int insert(Connection connection); int insertSelective(Connection connection); Connection selectByPrimaryKey(Long clientId); int updateByPrimaryKeySelective(Connection connection); int updateByPrimaryKey(Connection connection); Long selectMaxByAttributes(@Param("ip")String ip,@Param("vhost")String vhost,@Param("app")String app,@Param("tcUrl")String tcUrl,@Param("pageUrl")String pageUrl,@Param("reserver1")String reserver1,@Param("reserver2")String reserver2); List<Connection> selectByAttributes(@Param("pageIndex")Long pageIndex,@Param("pageSize")Long pageSize,@Param("orderProp")String orderProp,@Param("order")String order,@Param("ip")String ip,@Param("vhost")String vhost,@Param("app")String app,@Param("tcUrl")String tcUrl,@Param("pageUrl")String pageUrl,@Param("reserver1")String reserver1,@Param("reserver2")String reserver2); }
部分代码示例:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.easystudy.mapper.ConnectionMapper" > <resultMap id="BaseResultMap" type="com.easystudy.model.Connection" > <id column="clientId" property="clientId" jdbcType="BIGINT" /> <result column="ip" property="ip" jdbcType="VARCHAR" /> <result column="vhost" property="vhost" jdbcType="VARCHAR" /> <result column="app" property="app" jdbcType="VARCHAR" /> <result column="tcUrl" property="tcUrl" jdbcType="VARCHAR" /> <result column="pageUrl" property="pageUrl" jdbcType="VARCHAR" /> <result column="reserver1" property="reserver1" jdbcType="VARCHAR" /> <result column="reserver2" property="reserver2" jdbcType="VARCHAR" /> </resultMap> <sql id="Base_Column_List" > clientId,ip,vhost,app,tcUrl,pageUrl,reserver1,reserver2 </sql> <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long" > select <include refid="Base_Column_List" />from t_connection where clientId = #{clientId,jdbcType=BIGINT} </select> <delete id="deleteByPrimaryKey" parameterType="java.lang.Long" > delete from t_connection where clientId = #{clientId,jdbcType=BIGINT} </delete> <select id="selectMaxByAttributes" resultType="long"> select count(clientId) from t_connection <where> <if test="ip != null and ip.length() > 0"> ip like CONCAT('%',#{ip},'%') </if> <if test="vhost != null and vhost.length() > 0"> and vhost like CONCAT('%',#{vhost},'%') </if> <if test="app != null and app.length() > 0"> and app like CONCAT('%',#{app},'%') </if> <if test="tcUrl != null and tcUrl.length() > 0"> and tcUrl like CONCAT('%',#{tcUrl},'%') </if> <if test="pageUrl != null and pageUrl.length() > 0"> and pageUrl like CONCAT('%',#{pageUrl},'%') </if> <if test="reserver1 != null and reserver1.length() > 0"> and reserver1 like CONCAT('%',#{reserver1},'%') </if> <if test="reserver2 != null and reserver2.length() > 0"> and reserver2 like CONCAT('%',#{reserver2},'%') </if> </where> </select> <select id="selectByAttributes" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from t_connection <where> <if test="ip != null and ip.length() > 0"> ip like CONCAT('%',#{ip},'%') </if> <if test="vhost != null and vhost.length() > 0"> and vhost like CONCAT('%',#{vhost},'%') </if> <if test="app != null and app.length() > 0"> and app like CONCAT('%',#{app},'%') </if> <if test="tcUrl != null and tcUrl.length() > 0"> and tcUrl like CONCAT('%',#{tcUrl},'%') </if> <if test="pageUrl != null and pageUrl.length() > 0"> and pageUrl like CONCAT('%',#{pageUrl},'%') </if> <if test="reserver1 != null and reserver1.length() > 0"> and reserver1 like CONCAT('%',#{reserver1},'%') </if> <if test="reserver2 != null and reserver2.length() > 0"> and reserver2 like CONCAT('%',#{reserver2},'%') </if> </where> <if test="orderProp != null and orderProp.length() > 0"> order by <choose> <when test="order != null and order != 'desc' and order != 'asc'"> ${orderProp} asc </when> <when test="order == null"> ${orderProp} </when> <otherwise> ${orderProp} ${order} </otherwise> </choose> </if> limit #{pageIndex}, #{pageSize} </select> <insert id="insert" parameterType="com.easystudy.model.Connection" > insert into t_connection(clientId,ip,vhost,app,tcUrl,pageUrl,reserver1,reserver2) values(#{clientId,jdbcType=BIGINT},#{ip,jdbcType=VARCHAR},#{vhost,jdbcType=VARCHAR},#{app,jdbcType=VARCHAR},#{tcUrl,jdbcType=VARCHAR},#{pageUrl,jdbcType=VARCHAR},#{reserver1,jdbcType=VARCHAR},#{reserver2,jdbcType=VARCHAR}) </insert> <insert id="insertSelective" parameterType="com.easystudy.model.Connection" > insert into t_connection <trim prefix="(" suffix=")" suffixOverrides="," > <if test="clientId != null" > clientId, </if> <if test="ip != null" > ip, </if> <if test="vhost != null" > vhost, </if> <if test="app != null" > app, </if> <if test="tcUrl != null" > tcUrl, </if> <if test="pageUrl != null" > pageUrl, </if> <if test="reserver1 != null" > reserver1, </if> <if test="reserver2 != null" > reserver2, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides="," > <if test="clientId != null" > #{clientId,jdbcType=BIGINT}, </if> <if test="ip != null" > #{ip,jdbcType=VARCHAR}, </if> <if test="vhost != null" > #{vhost,jdbcType=VARCHAR}, </if> <if test="app != null" > #{app,jdbcType=VARCHAR}, </if> <if test="tcUrl != null" > #{tcUrl,jdbcType=VARCHAR}, </if> <if test="pageUrl != null" > #{pageUrl,jdbcType=VARCHAR}, </if> <if test="reserver1 != null" > #{reserver1,jdbcType=VARCHAR}, </if> <if test="reserver2 != null" > #{reserver2,jdbcType=VARCHAR}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="com.easystudy.model.Connection" > update t_connection <set> <if test="clientId != null" > clientId = #{clientId,jdbcType=BIGINT}, </if> <if test="ip != null" > ip = #{ip,jdbcType=VARCHAR}, </if> <if test="vhost != null" > vhost = #{vhost,jdbcType=VARCHAR}, </if> <if test="app != null" > app = #{app,jdbcType=VARCHAR}, </if> <if test="tcUrl != null" > tcUrl = #{tcUrl,jdbcType=VARCHAR}, </if> <if test="pageUrl != null" > pageUrl = #{pageUrl,jdbcType=VARCHAR}, </if> <if test="reserver1 != null" > reserver1 = #{reserver1,jdbcType=VARCHAR}, </if> <if test="reserver2 != null" > reserver2 = #{reserver2,jdbcType=VARCHAR}, </if> </set> where clientId = #{clientId,jdbcType=BIGINT} </update> <update id="updateByPrimaryKey" parameterType="com.easystudy.model.Connection" > update t_connection clientId = #{clientId,jdbcType=BIGINT}, ip = #{ip,jdbcType=VARCHAR}, vhost = #{vhost,jdbcType=VARCHAR}, app = #{app,jdbcType=VARCHAR}, tcUrl = #{tcUrl,jdbcType=VARCHAR}, pageUrl = #{pageUrl,jdbcType=VARCHAR}, reserver1 = #{reserver1,jdbcType=VARCHAR}, reserver2 = #{reserver2,jdbcType=VARCHAR} where clientId = #{clientId,jdbcType=BIGINT} </update> </mapper>
3个表生成对应3个实体类-持久化对象(PO)
部分代码示例:
package com.easystudy.model; import lombok.Data; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; /** * @文件名称: Connection.java * @功能描述: 系统实体类 * @版权信息: www.easystudy.com * @技术交流: 961179337(QQ群) * @编写做者: lixx2048@163.com * @联系方式: 941415509(QQ) * @开发日期: 2020年10月19日 * @历史版本: V1.0 * @备注信息: */ @Data @NoArgsConstructor @AllArgsConstructor public class Connection { private Long clientId; private String ip; private String vhost; private String app; private String tcUrl; private String pageUrl; private String reserver1; private String reserver2; }
包括跨域访问配置、Swagger配置以及MVC配置类
部分示例代码:
package com.easystudy.conf; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; /** * @文件名称: CORSConfig.java * @功能描述: 跨域访问配置 * @版权信息: www.easystudy.com * @技术交流: 961179337(QQ群) * @编写做者: lixx2048@163.com * @联系方式: 941415509(QQ) * @开发日期: 2020年10月19日 * @历史版本: V1.0 * @备注信息: */ @Configuration public class CORSConfig { @Bean public CorsFilter corsFilter() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); final CorsConfiguration config = new CorsConfiguration(); // 容许cookies跨域 config.setAllowCredentials(true); // #容许向该服务器提交请求的URI,*表示所有容许,在SpringMVC中,若是设成*,会自动转成当前请求头中的Origin config.addAllowedOrigin("*"); // #容许访问的头信息,*表示所有 config.addAllowedHeader("*"); // 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了 config.setMaxAge(18000L); // 容许提交请求的方法,*表示所有容许 config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }
swagger访问地址:
http://localhost:6666/hello/druid/datasource.html
swagger接口文档:
后台系统错误码VO值对象类,提供json与实体的转化,支持携带错误码和错误描述
部分示例代码:
package com.easystudy.error; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; /** * @文件名称: ReturnValue.java * @功能描述: 系统返回值dto定义 * @版权信息: www.easystudy.com * @技术交流: 961179337(QQ群) * @编写做者: lixx2048@163.com * @联系方式: 941415509(QQ) * @开发日期: 2020年10月19日 * @历史版本: V1.0 * @备注信息: */ @JsonInclude(Include.NON_NULL) public class ReturnValue<T> { private Integer error = 0; // 错误 private String description = ""; // 错误描述 private T value; // 返回值【当error为ERROR_NO_SUCCESS才有可能返回值-判断值是否为空】 // 成功不带返回值 public ReturnValue(){ this.error = ErrorCode.ERROR_SUCCESS.getError(); this.description = "成功"; } // 成功带返回值 public ReturnValue(T value){ if(null == value){ this.error = ErrorCode.ERROR_NOT_FOUND.getError(); this.description = "没有找到你须要的资源"; } else { this.error = ErrorCode.ERROR_SUCCESS.getError(); this.description = "成功"; this.value = value; } } // 返回错误 public ReturnValue(ErrorCode error){ this.error = error.getError(); this.description = error.getDescription(); } // 返回错误--对错误描述进行更改 public ReturnValue(ErrorCode error, String description){ this.error = error.getError(); this.description = description; } // 返回错误 public ReturnValue(Integer error){ this.error = error; this.description = ErrorCode.getDescription(error); } public ReturnValue(Integer error, String description){ this.error = error; this.description = description; } public Integer getError() { return error; } public boolean success(){ return error == ErrorCode.ERROR_SUCCESS.getError(); } public void setError(Integer error) { this.error = error; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
自动生成系统配置文件
系统配置文件以下所示:
server: port: 6666 servlet: context-path: /hello #nio的web服务配置 undertow: #为工做者建立的I/O线程数 io-threads: 20 #工做者线程数量 worker-threads: 50 #访问日志 accesslog: enabled: false spring: application: name: hello #NOSQL redis: host: localhost port: 6379 password: timeout: 6000 pool: max-active: 20 #链接池最大链接数(使用负值表示没有限制) max-wait: -1 max-idle: 8 min-idle: 0 #数据源 datasource: name: mclz type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/mclz?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false username: root password: root initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 'x' validation-query-timeout: 6 test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: false max-pool-prepared-statement-per-connection-size: 20 filters: stat connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 logging: config: classpath:logback.xml mybatis: mapper-locations: classpath:mapping/*.xml type-aliases-package: com.easystudy.model
示例代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>欢迎登陆hello系统</title> <link rel="stylesheet" type="text/css" href="../css/login.css"/> </head> <body> <h1>Hello World!</h1> </body> </html>
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.easystudy</groupId> <artifactId>hello</artifactId> <version>1.0.1.1</version> <!-- spring boot项目 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <!-- 项目属性:子模块不能引用父项目的properties变量 --> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> <lombok.version>1.16.20</lombok.version> </properties> <!-- 项目依赖管理声明,统一管理项目依赖的版本信息,继承项目无需声明版本 --> <dependencyManagement> <!-- 依赖管理 --> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!-- jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- hystrix仪表盘 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId> <version>${hystrix.dashboard}</version> </dependency> <!-- Spring-Mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <!-- 阿里巴巴druid数据库链接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.9</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!-- 阿里巴巴json序列化反序列化 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.46</version> </dependency> <!-- 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.5</version> </dependency> <!-- RESTFul接口文档:经过接口访问文档:http://localhost:8762/swagger-ui.html,8762为服务配置的监听端口,默认8080 feign Finchley.RELEASE 2.0.3 版本与feign版本冲突,使用feign必需要配置2.5.0以上版本 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.8.0</version> </dependency> <!-- feign:自己支持Hystrix的 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.4.RELEASE</version> </dependency>--> <!-- 远程调用:2.0.0.RELEASE版本会致使服务注册到nacos上 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.1.2.RELEASE</version> </dependency> <!-- reids做为缓存 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.1.4.RELEASE</version> </dependency> <!-- 文件上传 --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> <!-- httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.4.1</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.1</version> </dependency> <!-- junit测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- SpringMVC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- RESTFul接口文档:经过接口访问文档:http://localhost:8762/swagger-ui.html,8762为服务配置的监听端口,默认8080 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency> <!-- 配置文件读取 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> <!--Lombok:消除模板代码--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- logback日志包 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- Spring-Mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!-- 阿里巴巴druid数据库链接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> </dependencies> <!-- 编译插件 --> <build> <plugins> <!--spring boot maven插件--> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!-- 包含本地jar包 --> <configuration> <includeSystemScope>true</includeSystemScope> </configuration> </plugin> </plugins> </build> </project>
系统配置以下:
<configuration> <!-- 每一个logger都关联到logger上下文,默认上下文名称为“default”,用于区分不一样应用程序的记录。一旦设置,不能修该 --> <contextName>hello</contextName> <!--配置常量,在后面的配置中使用 --> <property name="PROJECT_NAME" value="hello" /> <!--配置常量,在后面的配置中使用 --> <property name="COMPANY_NAME" value="hello" /> <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 --> <property name="LOG_HOME" value="/home/${COMPANY_NAME}/logs/${PROJECT_NAME}" /> <!--定义日志输出格式:左对齐级别 -日期 -消息 换行--> <property name="LOG_PATTERN" value="[%-5p] - %d{yyyy-MM-dd HH:mm:ss} - %msg%n" /> <!-- 定义日志输出字符集 --> <property name="LOG_CHARSET" value="UTF-8" /> <!-- 调试日志:输出控制台 --> <appender name="CONSOLE-LOG" class="ch.qos.logback.core.ConsoleAppender"> <!--定义输出格式--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${LOG_PATTERN}</pattern> <charset>${LOG_CHARSET}</charset> </encoder> <!-- 比INFO小的日志级别不会被打印出来:打印全部级别 --> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>DEBUG</level> </filter> </appender> <!-- 正常INFO级别日志:输出文件--> <appender name="INFO-LOG" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--定义输出文件名--> <file>${LOG_HOME}/info/${PROJECT_NAME}.log</file> <!--定义输出格式--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${LOG_PATTERN}</pattern> <charset>${LOG_CHARSET}</charset> </encoder> <!-- 比该配置的小的日志级别不会被打印出来 --> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> <!--定义日志滚动的策略--> <!-- TimeBasedRollingPolicy和SizeBasedTriggeringPolicy冲突,使用SizeAndTimeBasedRollingPolicy --> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--定义文件滚动时的文件名的格式:dys-warn_2019-01-10-%i.log--> <fileNamePattern>${LOG_HOME}/info/${PROJECT_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern> <!-- 每一个文件大小最大200M --> <maxFileSize>200MB</maxFileSize> <!-- 最大保存n天 --> <maxHistory>7</maxHistory> <!-- 日志总保存量为2G:该属性在 1.1.6版本后 才开始支持--> <totalSizeCap>2GB</totalSizeCap> <!-- 启动时清除无效历史数据 --> <cleanHistoryOnStart>true</cleanHistoryOnStart> </rollingPolicy> </appender> <!-- 警告日志:输出文件--> <appender name="WARN-LOG" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--定义输出文件名--> <file>${LOG_HOME}/warn/${PROJECT_NAME}.log</file> <!--定义输出格式--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${LOG_PATTERN}</pattern> <charset>${LOG_CHARSET}</charset> </encoder> <!-- 比该配置的小的日志级别不会被打印出来 --> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>WARN</level> </filter> <!--定义日志滚动的策略--> <!-- TimeBasedRollingPolicy和SizeBasedTriggeringPolicy冲突,使用SizeAndTimeBasedRollingPolicy --> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--定义文件滚动时的文件名的格式:dys-warn_2019-01-10-%i.log--> <fileNamePattern>${LOG_HOME}/warn/${PROJECT_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern> <!-- 每一个文件大小最大200M --> <maxFileSize>200MB</maxFileSize> <!-- 最大保存n天 --> <maxHistory>7</maxHistory> <!-- 日志总保存量为2G:该属性在 1.1.6版本后 才开始支持--> <totalSizeCap>2GB</totalSizeCap> <!-- 启动时清除无效历史数据 --> <cleanHistoryOnStart>true</cleanHistoryOnStart> </rollingPolicy> </appender> <!-- 错误日志:输出文件--> <appender name="ERROR-LOG" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--定义输出文件名--> <file>${LOG_HOME}/error/${PROJECT_NAME}.log</file> <!--定义输出格式--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${LOG_PATTERN}</pattern> <charset>${LOG_CHARSET}</charset> </encoder> <!-- 比该配置的小的日志级别不会被打印出来 --> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> <!--定义日志滚动的策略--> <!-- TimeBasedRollingPolicy和SizeBasedTriggeringPolicy冲突,使用SizeAndTimeBasedRollingPolicy --> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--定义文件滚动时的文件名的格式:dys-warn_2019-01-10.log--> <fileNamePattern>${LOG_HOME}/error/${PROJECT_NAME}_%d{yyyy-MM-dd}-%i.log</fileNamePattern> <!-- 每一个文件大小最大500M --> <maxFileSize>500MB</maxFileSize> <!-- 最大保存n天 --> <maxHistory>15</maxHistory> <!-- 日志总保存量为2G:该属性在 1.1.6版本后 才开始支持--> <totalSizeCap>2GB</totalSizeCap> </rollingPolicy> </appender> <!-- 根日志输出:root为默认日志输出INFO或以上级别(additivity=true时被继承,或name类没法找到时被继承--> <root level="INFO"> <!-- 输出到控制台 --> <appender-ref ref="CONSOLE-LOG" /> <!-- 输出到各个类型日志文件 --> <appender-ref ref="INFO-LOG" /> <appender-ref ref="WARN-LOG" /> <appender-ref ref="ERROR-LOG" /> </root> <!-- 调试日志:输出控制台 --> <appender name="CONSOLE-LOG2" class="ch.qos.logback.core.ConsoleAppender"> <!--定义输出格式--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${LOG_PATTERN}</pattern> <charset>${LOG_CHARSET}</charset> </encoder> <!-- 比DEBUG小的日志级别不会被打印出来:打印全部级别 --> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>DEBUG</level> </filter> </appender> <!-- mybatis输出:不继承根配置 --> <logger name="com.easystudy.mapper" level="DEBUG" additivity="false"> <!-- 输出到控制台 --> <appender-ref ref="CONSOLE-LOG2" /> </logger> </configuration>
以上就是完整的、最基础的项目的代码生成介绍,该项目主要经过以下方式实现:
系统生成代码经过约定的值进行的,包括日志存储位置、名称、默认依赖。
代码风格使用比较规范的样式进行生成
全部的代码是依赖模板生成的,有模板的拷贝替换、模板依葫芦画瓢,这里可能有做者代码的身影在里面,见谅!
源码获取、合做、技术交流请获取以下联系方式:
QQ交流群:961179337
微信帐号:lixiang6153 公众号:IT技术快餐 电子邮箱:lixx2048@163.com