文章目录
分类表的设计
drop table if exists `category`;
create table `category`
(
`id` bigint not null comment 'id',
`parent` bigint not null default 0 comment '父id',
`name` varchar(50) not null comment '名称',
`sort` int comment '顺序',
primary key (`id`)
) engine=innodb default charset=utf8mb4 comment='分类';
insert into category values(100,000,'前端开发',100),(101,100,'Vue',101),(102,100,'HTML&CSS',102);
insert into category values(200,000,'Java',200),(201,200,'基础应用',201),(202,200,'框架应用',202);
insert into category values(300,000,'Python',300),(301,300,'基础应用',301),(302,300,'进阶方向应用',302);
insert into category values(400,000,'数据库',400),(401,400,'MySQL',401);
insert into category values(500,000,'其他',500),(501,500,'服务器',501),(502,500,'开发工具',502),(503,500,'热门服务器语言',503);
后端代码实现
MyBatis生成Model实体类文件、Mapper接口文件和Mapper XML配置文件
generatorConfig.xml如下,
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="Mysql" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/test-database"
userId="test-user"
password="test@123">
</jdbcConnection>
<javaModelGenerator targetPackage="com.jepcc.test.model" targetProject="src\main\java">
</javaModelGenerator>
<sqlMapGenerator targetPackage="mapper" targetProject="src\main\resources">
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.jepcc.test.mapper" targetProject="src\main\java">
</javaClientGenerator>
<!-- <table tableName="ebook" domainObjectName="Ebook" ></table>-->
<table tableName="category" domainObjectName="Category" ></table>
</context>
</generatorConfiguration>
执行命令mybatis-generator:generate -e
,生成如下文件:
- Model实体类文件:
com.jepcc.test.model.Category
和com.jepcc.test.model.CategoryExample
- Mapper接口文件:
com.jepcc.test.mapper.CategoryMapper
- Mapper XML配置文件:
src/main/resources/mapper/CategoryMapper.xml
添加controller层和service层代码
//com.jepcc.test.controller.CategoryController
package com.jepcc.test.controller;
import com.jepcc.test.req.CategoryReq;
import com.jepcc.test.req.CategorySaveReq;
import com.jepcc.test.resp.CategoryResp;
import com.jepcc.test.resp.CommonResp;
import com.jepcc.test.resp.PageResp;
import com.jepcc.test.service.CategoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/category")
public class CategoryController {
private final static Logger LOG = LoggerFactory.getLogger(CategoryController.class);
@Resource
private CategoryService categoryService;
@GetMapping("/all")
public CommonResp all(){
CommonResp resp = new CommonResp();
List<CategoryResp> list = categoryService.all();
resp.setContent(list);
return resp;
}
@GetMapping("/list")
public CommonResp list(CategoryReq req){
CommonResp resp = new CommonResp();
PageResp<CategoryResp> list = categoryService.list(req);
resp.setContent(list);
return resp;
}
@PostMapping("/save")
public CommonResp save(@RequestBody CategorySaveReq req){
CommonResp resp = new CommonResp();
categoryService.save(req);
return resp;
}
@DeleteMapping("/delete/{id}")
public CommonResp delete(@PathVariable long id){
CommonResp resp = new CommonResp();
categoryService.delete(id);
return resp;
}
}
//com.jepcc.test.service.CategoryService
package com.jepcc.test.service;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jepcc.test.mapper.CategoryMapper;
import com.jepcc.test.model.Category;
import com.jepcc.test.model.CategoryExample;
import com.jepcc.test.req.CategoryReq;
import com.jepcc.test.req.CategorySaveReq;
import com.jepcc.test.resp.CategoryResp;
import com.jepcc.test.resp.PageResp;
import com.jepcc.test.util.CopyUtil;
import com.jepcc.test.util.SnowFlake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import java.util.List;
@Service
public class CategoryService {
@Resource
private CategoryMapper categoryMapper;
@Resource
private SnowFlake snowFlake;
private static final Logger LOG = LoggerFactory.getLogger(CategoryService.class);
public List<CategoryResp> all(){
CategoryExample categoryExample = new CategoryExample();
//按照sort字段asc排序
categoryExample.setOrderByClause("sort asc");
List<Category> list = categoryMapper.selectByExample(categoryExample);
List<CategoryResp> result = CopyUtil.copyList(list,CategoryResp.class);
return result;
}
public PageResp<CategoryResp> list(CategoryReq req){
CategoryExample categoryExample = new CategoryExample();
CategoryExample.Criteria criteria = categoryExample.or();
if(!ObjectUtils.isEmpty(req.getName())){
criteria.andNameLike("%"+req.getName()+"%");
}
PageHelper.startPage(req.getPage(),req.getSize());
List<Category> list = categoryMapper.selectByExample(categoryExample);
PageInfo<Category> pageInfo = new PageInfo<>(list);
List<CategoryResp> result = CopyUtil.copyList(list,CategoryResp.class);
PageResp<CategoryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(result);
return pageResp;
}
public void save(CategorySaveReq req){
Category category = CopyUtil.copy(req,Category.class);
if(!ObjectUtils.isEmpty(category.getId())){
// 更新
categoryMapper.updateByPrimaryKey(category);
}else{
// 插入
category.setId(snowFlake.nextId());
categoryMapper.insert(category);
}
}
public void delete(long id){
categoryMapper.deleteByPrimaryKey(id);
}
}
还新添加了一些请求类和响应类。
//com.jepcc.test.req.CategoryReq
package com.jepcc.test.req;
public class CategoryReq extends PageReq{
private Long id;
private Long parent;
private String name;
private Integer sort;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParent() {
return parent;
}
public void setParent(Long parent) {
this.parent = parent;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
}
//com.jepcc.test.req.CategorySaveReq
package com.jepcc.test.req;
public class CategorySaveReq {
private Long id;
private Long parent;
private String name;
private Integer sort;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParent() {
return parent;
}
public void setParent(Long parent) {
this.parent = parent;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
}
//com.jepcc.test.resp.CategoryResp
package com.jepcc.test.resp;
public class CategoryResp {
private Long id;
private Long parent;
private String name;
private Integer sort;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParent() {
return parent;
}
public void setParent(Long parent) {
this.parent = parent;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("CategoryResp{");
sb.append("id=").append(id);
sb.append(", parent=").append(parent);
sb.append(", name='").append(name).append('\'');
sb.append(", sort=").append(sort);
sb.append('}');
return sb.toString();
}
}
关于雪花算法前后端交互Long型精度丢失问题
这里提供了解决方案。本例使用第二种方法:统一配置。
所以在config目录下添加JacksonConfig类。
package com.jepcc.test.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder){
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}
前端代码实现
<template>
<a-layout>
<a-layout-content :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }">
<p>
<a-button type="primary" @click="handleAdd">新增</a-button>
</p>
<a-table :columns="columns" :data-source="categorys"
:loading="loading" :pagination="false" @change="handleChange">
<template #action="{text,record}">
<a-space>
<a-button type="primary" @click="edit(record)">编辑</a-button>
<a-popconfirm
title="删除后不可恢复,确认删除?"
ok-text="确定"
cancel-text="取消"
@confirm="confirm(record)"
>
<a-button type="danger">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
</a-layout-content>
</a-layout>
<a-modal :visible="visible" title="分类管理" @ok="handleOk" @cancel="handleCancel">
<a-form :model="category" :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }">
<a-form-item label="名称">
<a-input v-model:value="category.name" />
</a-form-item>
<a-form-item label="父分类">
<a-input v-model:value="category.parent" />
<a-select
v-model:value="category.parent"
ref="select"
>
<a-select-option value="0">无</a-select-option>
<a-select-option v-for="item in categorys" :key="item.id" :value="item.id" :disabled="category.id === item.id">
{{item.name}}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="排序">
<a-input v-model:value="category.sort" />
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts">
import {defineComponent, onMounted, ref} from 'vue';
import axios from "axios";
interface DataItem {
id:number,
name:string,
parent:number,
children?:DataItem[]
}
const arrayToTree = (arr:DataItem[],rootId:number) => {
let res:DataItem[] = [];
let len = arr.length;
if(len === 0) return res;
for(let i=0;i<len;i++){
const item = arr[i];
if(Number(item.parent) === Number(rootId)){
res.push(item);
const children = arrayToTree(arr,item.id);
if(children.length!==0){
item.children = children;
}
}
}
return res;
}
export default defineComponent({
name:"AdminCategory",
setup() {
const columns = [
{
dataIndex: 'name',
key: 'name',
title:"名称"
},
{
dataIndex: 'parent',
key: 'parent',
title:"父分类"
},
{
dataIndex: 'sort',
key: 'sort',
title:"排序"
},
{
title: 'Action',
key: 'action',
slots: { customRender: 'action' },
},
];
const loading = ref(false);
const categorys = ref();
const visible = ref(false);
const category = ref({
id:"",
parent:"",
name:"",
sort:""
});
const handleQuery = () => {
loading.value = true;
axios.get("/category/all").then(response => {
loading.value = false;
const data = response.data;
if(data.success){
var result = arrayToTree(data.content,0);
console.log("result of arrayToTree:",result);
categorys.value = result;
}
})
}
const handleSave = (params:any) => {
axios.post("/category/save",params).then(response => {
visible.value = false;
const data = response.data;
if(data.success){
handleQuery();
}else{
console.log("save failed");
}
})
}
const handleDelete = (record:any) => {
axios.delete("/category/delete/"+record.id).then(response => {
const data = response.data;
if(data.success){
handleQuery();
}else{
console.log("delete failed");
}
})
}
const handleChange = (p:any) => {
handleQuery();
}
const edit = (record:any) => {
visible.value = true;
category.value = JSON.parse(JSON.stringify(record));
}
const handleOk = () => {
handleSave(category.value);
}
const handleCancel = () => {
visible.value = false;
}
const confirm = (record:any) => {
handleDelete(record)
}
const handleSearch = () => {
handleQuery();
}
const handleAdd = () => {
category.value = {
id:"",
parent:"",
name:"",
sort:""
}
visible.value = true;
}
onMounted(() => {
handleQuery();
})
return {
categorys,
columns,
loading,
visible,
category,
handleQuery,
handleChange,
edit,
handleOk,
handleCancel,
confirm,
handleSearch,
handleAdd
};
}
});
</script>