[TOC]
在上篇文章中我们已经实现了自定义菜单了,我们可以根据自己的实际需求去定制自己需要的菜单,做好了这一步,接下来我们就可以开发新功能了。
我们就先从最简单的渠道管理开始。
还是老规矩,一个特别基础的细节我就不啰嗦了,如果大家阅读吃力,也可以先看看 vhr(https://github.com/lenve/vhr) 再看这个就容易多了。
1. 分配权限
我们依葫芦画瓢,首先在 sys_menu 中为渠道相关的操作添加权限,新增如下两条记录:
2008 就是渠道管理菜单项的 id。渠道管理将来就对应了这四个操作。
2. 渠道管理表
渠道管理比较简单,一张表,也不需要引用其他表,如下:
这个表很简单,没啥好说的。
3. 服务端接口开发
3.1 现有功能分析
用了这个脚手架,我也就懒得另起炉灶了,我们现在要写接口,接口该怎么写?我们可以参考一个他自己写好的,例如用户管理接口。
用户管理接口位置在 org.javaboy.web.controller.system.SysUserController#list
方法中:
1 2 3 4 5 6 7
| @PreAuthorize("@ss.hasPermi('system:user:list')") @GetMapping("/list") public TableDataInfo list(SysUser user) { startPage(); List<SysUser> list = userService.selectUserList(user); return getDataTable(list); }
|
大家看,首先通过权限注解确保用户具备相应的权限。这个权限注解对应的方法是 org.javaboy.framework.web.service.PermissionService#hasPermi
方法,具体的逻辑也并不难,当用户登录成功后,会查询出来当前用户的所有权限,并放到 LoginUser 对象中(这个在本系列的第一篇文章中已经讲过了),然后将之存入到 Redis 中,现在这里就是从 Redis 中取回 LoginUser 对象,然后拿出来用户的权限字符串,跟这里需要的权限字符串做比对。
由于这个脚手架自定义了一个 BaseController,里边封装了很多常用的操作,所有的业务 Controller 都是继承自这个 BaseController,所以这里的 startPage 方法其实就是 BaseController 中的方法,这个方法会自动开启分页功能,会从当前请求中提取出分页参数,然后进行查询。如果前端没有传递分页参数,那么默认查询第一页,查询 10 条数据。
接下来就是一个常规的查询操作,没啥好说的。
最后的 getDataTable 方法则是将数据包装成一个分页的 JSON 对象。
还有一点要捋清楚,就是这个脚手架是一个多模块项目,所有的借口定义统一在 admin 中,不同的功能对应不同的模块,例如用户管理相关的功能都在 system 这个模块中。
好了,看懂这个,我们就照猫画虎。
3.2 创建工程
首先,我们新建一个自己的功能模块,这是一个 maven 项目,叫做 tienchin-channel。
这里我想用 MyBatis-Plus 来做,因此我先修改父工程的 dependencyManagement,将 mp 的版本号统一管理起来,同时也将新建模块加进去,方便后期引用的时候进行版本号统一管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <properties> <mybatis-plus-boot-starter.version>3.5.1</mybatis-plus-boot-starter.version> <mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus-boot-starter.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>${mybatis-plus-generator.version}</version> </dependency> <dependency> <groupId>org.javaboy</groupId> <artifactId>tienchin-channel</artifactId> <version>${tienchin.version}</version> </dependency> </dependencies> </dependencyManagement>
|
创建完成后,我们手动修改一下 tienchin-channel 的 pom.xml 文件,照着脚手架 system 模块的改即可:
1 2 3 4 5 6 7 8 9 10
| <description> 渠道管理模块 </description> <dependencies> <dependency> <groupId>org.javaboy</groupId> <artifactId>tienchin-common</artifactId> </dependency> </dependencies>
|
接下来,在 admin 模块中,依赖当前新建的 tienchin-channel 模块和 mp 的代码自动生成依赖,如下:
1 2 3 4 5 6 7 8 9
| <dependency> <groupId>org.javaboy</groupId> <artifactId>tienchin-channel</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <scope>test</scope> </dependency>
|
另外依赖我还有一些细微的调整,例如为父模块添加了 Spring Boot 作为其 parent 等,这些我就不逐一说明了,大家可以在文末下载源码查看。
3.3 配置 MP
这个脚手架中虽然用了 MyBatis 的 starter,但是实际上还是自己手动配置的 MyBatis,所以当我们使用 MP 的时候,并不能像在 Spring Boot 中使用 MP 那样,加个依赖就行了,我们还需要手动改一下配置。
首先我们将 mp 的依赖放到 common 模块中,毕竟将来无论是 framework 还是我们新建的 tienchin-channel 都依赖 common 模块,如下:
tienchin-common/pom.xml:
1 2 3 4
| <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency>
|
然后,MyBatis 的配置是在 framework 模块中,具体代码在 tienchin-framework/src/main/java/org/javaboy/framework/config/MyBatisConfig.java
位置,我们直接在此进行修改即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage"); String mapperLocations = env.getProperty("mybatis.mapperLocations"); String configLocation = env.getProperty("mybatis.configLocation"); typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); VFS.addImplClass(SpringBootVFS.class); final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); sessionFactory.setTypeAliasesPackage(typeAliasesPackage); sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ","))); sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); return sessionFactory.getObject(); }
|
小伙伴们看一下,在配置的过程中,将原本的 SqlSessionFactoryBean 改为 MybatisSqlSessionFactoryBean,其他都不变即可。
如此,我们的 MP 就配置好了。
3.4 生成代码
接下来,我们在 admin 模块的单元测试中,通过如下代码来生成一下 channel 对应的实体类啥的,如果大家对这个自动生成代码的不熟悉的话,可以看看这篇文章:自动生成实体类,哪个最佳?:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Generator { @Test public void channelGenerator() { FastAutoGenerator.create("jdbc:mysql:///tienchin?serverTimezone=Asia/Shanghai&useSSL=false", "root", "123") .globalConfig(builder -> { builder.author("javaboy") .disableOpenDir() .fileOverride() .outputDir("/Users/sang/workspace/workspace02/tienchin/tienchin-channel/src/main/java"); }) .packageConfig(builder -> { builder.parent("org.javaboy") .moduleName("channel") .pathInfo(Collections.singletonMap(OutputFile.xml, "/Users/sang/workspace/workspace02/tienchin/tienchin-channel/src/main/resources/mapper/channel")); }) .strategyConfig(builder -> { builder.addInclude("tienchin_channel") .addTablePrefix("tienchin_"); }) .templateEngine(new FreemarkerTemplateEngine()) .execute(); } }
|
自动生成的源码自带 Controller,我们将其删除,重新在 admin 模块中创建对应的 ChannelController 即可。
对照现有的任意一个 Controller,我们写出来自己的 Controller,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @RestController @RequestMapping("/tienchin/channel") public class ChannelController extends BaseController {
@Autowired IChannelService channelService;
@PreAuthorize("@ss.hasPermi('tienchin:channel:query')") @GetMapping("/list") public TableDataInfo getChannelList() { startPage(); List<Channel> list = channelService.list(); return getDataTable(list); } @PreAuthorize("@ss.hasPermi('tienchin:channel:add')") @Log(title = "渠道管理" , businessType = BusinessType.INSERT) @PostMapping("/") public AjaxResult add(@Validated @RequestBody Channel channel) { channel.setCreateBy(getUsername()); return toAjax(channelService.saveChannel(channel)); }
@PreAuthorize("@ss.hasPermi('tienchin:channel:query')") @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable Long id) { return AjaxResult.success(channelService.getById(id)); }
@PreAuthorize("@ss.hasPermi('tienchin:channel:edit')") @Log(title = "渠道管理" , businessType = BusinessType.UPDATE) @PutMapping("/") public AjaxResult edit(@Validated @RequestBody Channel channel) { channel.setUpdateBy(getUsername()); channel.setUpdateTime(LocalDateTime.now()); return toAjax(channelService.saveOrUpdate(channel)); }
@PreAuthorize("@ss.hasPermi('tienchin:channel:remove')") @Log(title = "渠道管理" , businessType = BusinessType.DELETE) @DeleteMapping("/{channelIds}") public AjaxResult remove(@PathVariable Long[] channelIds) { return toAjax(channelService.removeBatchByIds(Arrays.asList(channelIds))); }
}
|
都是常规操作,没啥特别值得说的地方。
@Log 是脚手架中定义的日志记录注解,加一个这个注解,会自动将当前的操作记录到 sys_oper_log 表中,像下面这样:
@PreAuthorize 操作权限就按一开始在数据库中配置的内容即可。
照猫画虎,很快就写出来这样一个接口。
4. 开发前端页面
接下来我们来整前端页面,前端页面我们在第二篇文章中提到过,该功能对应的页面是 src/views/tienchin/channel/index.vue
,所以我们只需要修改该页面即可,这个修改,我们也找一个参照物,找一个也是表格的页面改一下就行了,例如 src/views/system/dict/index.vue
,这是字典管理的页面,我们就照着这个来改就行了,前端的代码量太大了,我就不全部贴出来了,我挑几个关键的地方来说一下。
4.1 网络请求
前端是每一个 .vue
文件都将自己所需的网络请求封装在一个 js 文件中,然后将来在 .vue
文件中直接引用。
例如关于数据字典的所有请求封装在 src/api/system/dict/type.js
文件中,我照猫画虎写了关于 channel 的所有网络请求:
src/api/channel/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import request from '@/utils/request'
export function listChannel(query) { return request({ url: '/tienchin/channel/list', method: 'get', params: query }) }
export function getChannel(channelId) { return request({ url: '/tienchin/channel/' + channelId, method: 'get' }) }
export function addChannel(data) { return request({ url: '/tienchin/channel/', method: 'post', data: data }) }
export function updateChannel(data) { return request({ url: '/tienchin/channel/', method: 'put', data: data }) }
export function delChannel(channelIds) { return request({ url: '/tienchin/channel/' + channelIds, method: 'delete' }) }
|
4.2 页面展示
页面展示有一个地方需要和大家聊一聊。
就是当用户登录成功之后,前端会调用服务端的接口查看当前用户信息,包括用户的权限信息,而且前端还封装了一个空闲显示或者隐藏的工具,位置在 src/directive/permission/hasPermi.js
,这个工具最终被做成了一个自定义指令,这样,我们在展示每一个按钮的时候,可以加上这个指令,将来就会自动根据用户是否具备相应的权限来展示相应的按钮,例如下面这几个按钮:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <el-col :span="1.5"> <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['tienchin:channel:add']" >新增 </el-button> </el-col> <el-col :span="1.5"> <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate" v-hasPermi="['tienchin:channel:edit']" >修改 </el-button> </el-col> <el-col :span="1.5"> <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete" v-hasPermi="['tienchin:channel:remove']" >删除 </el-button> </el-col>
|
每个按钮上都有一个 v-hasPermi 标签来表述这个按钮将来显示的条件。
另外,前端也使用到了数据字典,也就是一些常见的字段取值我们将之固定下来了,在前端直接引用即可。数据字典本身对应的表是 sys_dict_data
和 sys_dict_type
,像下面这样(下图为 sys_dict_data
表,关于他这个里边的数据字典,后面有空了松哥可以再整一篇文章和大家分析具体用法):
需要用到哪条记录,就在 vue 文件定义的时候声明就行了,像下面这样:
这样,后期就可以直接引用这个变量了,如下:
1 2 3 4 5
| <el-table-column label="渠道状态" align="center" prop="status"> <template slot-scope="scope"> <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/> </template> </el-table-column>
|
options 其实就引用了数据字典中的值。
关于这个页面其他的内容就都是常规操作了,会 vhr 基本上都能看懂,我也就不啰嗦了。
最终弄出来的页面如下:
5. 小结
好啦,今天就先聊这么多,源码地址如下: