刘一辰的软件工程随笔
软件构造实验作业
班级:信1905-2班学号:20193897姓名:刘一辰
一、实验要求
根据参考资料,学习Guns框架的使用并如下任务:
任务一:导入并配置Guns框架
任务二:阅读Gans的源码并对每一部分的功能进行介绍
任务三:基于Gans完成一个汽车信息管理系统
二、实验步骤
任务一:导入并配置Guns框架
首先在Gitee下载项目
打开之后不着急运行项目,先打开idea -> file -> settings,导入maven配置
库名要和数据库的库名一致,如果数据库没有这个库的话需要创建。
数据库内容不用初始化,因为项目启动后会自动初始化表。之后运行,Guns的启动类即可
打开 http://localhost:8080 (opens new window), 输入默认账号密码:admin/123456,即可进入系统。
任务二:阅读Gans的源码并对每一部分的功能进行介绍
学习一下 github 上面用 Tensorflow 写的 GAN 代码。
dataset.py
import multiprocessing # 允许程序员充分利用多个核心
import tensorflow as tf # 导入 tensorflow
def make_anime_dataset(img_paths,batch_size,resize=64,drop_remainder=True,shuffle=True,repeat=1):
# 生成动漫数据集(图片路径, 批量大小, ...)
@tf.function
Tip: 函数装饰器
# 用函数 function1 去装饰函数 function2
def function1(function): # 函数 function1 的形式参数传入一个函数
# ...
function()
return A
@function1
def function2():
# ...
# 上面的代码等价于下面的代码
function2=function1(function2)
# 相当于用函数 function1 处理了一下 function2
Tip: tf.function
TensorFlow 2 中,只需要将我们希望以图执行模式(Graph Execution)/ 而非及时执行模式(Eager Execution)的代码写成一个函数,并在函数前加上@tf.function 进行装饰就可以了。
def make_anime_dataset(img_paths, batch_size, resize=64, drop_remainder=True, shuffle=True, repeat=1): # img_paths 是一个数组
@tf.function # 下面函数内的代码以图执行模式执行
def _map_fn(img):
img = tf.image.resize(img, [resize, resize]) # 将图片大小变为 64x64
img = tf.clip_by_value(img,0,255) # 将张量 img 中的数值限制在 0~255 之内
img = img / 127.5 - 1 # 将图片的值归一化为 -1 ~ 1(猜测后面的会用 tanh 作为激活函数)
return img # 返回归一化处理后的图片张量
dataset = disk_imag_batch_dataset(img_paths,
batch_size,
drop_remainder=drop_remainder,
map_fn=map_fn,
shuffle=shuffle,
repeat=repeat)
img_shape = (resize, resize, 3) # 图片的张量形状 (64x64x3) 3 个通道为 RGB 三个通道
len_dataset = len(img_paths) // batch_size # 有多少个 batch // 表示整除
return dataset, img_shape, len_dataset
def batch_dataset(dataset,
batch_size,
drop_remainder=True,
n_prefetch_batch=1,
flter_fn=None,
n_map_threads=None,
filter_after_map=False,
shuffle=True,
shuffle_buffer_size=None,
repeat=None):
# 对数据集进行 洗牌、筛选、处理、打包
if n_map_threads is None: # 如果参数 n_map_threads 传入值为 None
n_map_threads = multiprocessing.cpu_count() # 返回 cpu 的核数 目前 cpu 应该都是 4 核的吧,老一点的 cpu 有 2 核的
if shuffle and shuffle_buffer_size is None: # 如果这两个参数其中一个是 None
shuffle_buffer_size = max(batch_size*128,2048) # 最小的用于打乱的 buffer_size 是2048,其他的是批量大小的 128 倍
# 在对 dataset 执行 map 之前,先执行 shuffle 是更有效率的,因为 map 有时很耗时间
if shuffle: # 如果需要打乱
dataset=dataset.shuffle(shuffle_buffer_size) # 对数据集进行打乱
if not filter_after_map:
if filter_fn:
dataset = dataset.filter(filter_fn) # 对数据集进行过滤,过滤标准依照函数filter_fn
if map_fn:
dataset = dataset.map(map_fn, num_parallel_calls=n_map_threads) # 对数据集中的所有数据进行操作,多线程并行操作
else: # 如果不需要过滤
if map_fn:
dataset = dataset.map(map_fn, num_parallel_calls=n_map_threads)
if filter_fn:
dataset = dataset.filter(filter_fn)
dataset = dataset.batch(batch_size, drop_remainder=drop_remainder) # 将数据集分成一个一个小块, drop_remainder:对于最后一个不足 batch_size 的数据是保留还是删除,默认选择保留。
dataset = dataset.repeat(repeat).prefetch(n_prefetch_batch) # prefetch 放在最后提供一个 software pipelining 机制。使得 GPU 在训练上一次准备的数据时,CPU 去准备下一次需要的数据。如果单个训练步骤消耗 n 个元素,则添加 prefetch(n)。
return dataset
def memory_data_batch_dataset(memory_data, #list/ndarray/Tensor
batch_size,
drop_remainder=True,
n_prefectch_batch=1,
filter_fn=None,
map_fn=None,
n_map_threads=None,
filter_after_map=False,
shuffle_buffer_size=None,
repeat=None):
# 从内存中读取数据、制作数据集
dataset = tf.data.Dataset.from_tensor_slices(memory_data) # 将 memory_data 制作成数据集
dataset = batch_dataset(dataset,
batch_size,
drop_remainder=drop_remainder,
n_prefetch_batch=n_prefetch_batch,
filter_fn=filter_fn,
map_fn=map_fn,
n_map_threads=n_map_threads,
filter_after_map=filter_after_map,
shuffle=shuffle,
shuffle_buffer_size=shuffle_buffer_size,
repeat=repeat) # 对 dataset 洗牌、筛选、处理、打包
return dataset
def disk_image_batch_dataset(img_paths,
batch_size,
labels=None,
drop_remainder=True,
n_prefetch_batch=1,
filter_fn=None,
map_fn=None,
n_map_threads=None,
filter_after_map=False,
shuffle=True,
shuffle=buffer_size=None,
repeat=None):
# 从磁盘数据中读取数据制作数据集
if label is None:
memory_data = img_paths # 如果没有标签
else:
memory_data = (img_patchs,labels) # 如果有标签
def parse_fn(path, *label): # * 表示接受一个元组、** 表示接受一个字典
# 读取文件 返回一个元组
img = tf.io.read_file(path) # 读取文件
img = tf.image.decode_png(img,3) # 解码 png 图片 通道数固定到 RGB 三个通道
return (img,) + label # 合成一个元组
if map_fn: # 融合 map_fn 和 parse_fn 如果 map_fn 有传入值
def map_fn_(*args):
return map_fn(*parse_fn(*args)) # 对图片进行处理
else:
map_fn_ = parse_fn # 将 map_fn 和 parse_fn合成为一个函数 map_fn_
# 从磁盘中读取数据,其实本质上也是把磁盘中的数据读取到内存中,最后从内存中制作数据集
dataset = memory_data_batch_dataset(memory_data,
batch_size,
drop_remainder=drop_remainder,
n_prefetch_batch=n_prefetch_batch,
filter_fn=filter_fn,
map_fn=map_fn_,
n_map_threads=n_map_threads,
filter_after_map=filter_after_map,
shuffle=shuffle,
shuffle_buffer_size=shuffle_buffer_size,
repeat=repeat)
return dataset
gan.py
import os # 导入 os 系统模块
import tensorflow as tf # 导入 tensorflow 模块
from tensorflow import keras # 导入 keras 模型部分
os.environp['TF_CPP_MIN_LOG_LEVEL']='2' # 屏蔽 INFO 和 WARNING 输出 ERRPR 和 FATAL
class Generator(keras.Model):
def __init__(self):
# 显示确定一些需要的参数、其实可以不用在__init__中定义,直接在 call 中使用即可。
super(Generator,self).__init__() # 向父类注册
self.fc=keras.layers.Dense(3*3*512)
self.conv1=keras.layers.Conv2DTranspose(256,3,3,'valid') # 反卷积 kernel_size=3 filters=256,padding='valid'
self.bn1=keras.layers.BatchNormalization()
self.conv2=keras.layers.Conv2DTranspose(128,5,2,'valid')
self.b2=keras.layers.BatchNormalization()
self.conv3=keras.layers.Conv2DTranspose(3,4,3,'valid')
def call(self, inputs, training=None, mask=None):
x=self.fc(inputs)
x=tf.reshape(x,[-1,3,3,512])
x=tf.nn.leaky_relu(x)
x=self.conv1(x)
x=self.bn1(x,training=training)
x=tf.nn.leaky_relu(x)
x=self.conv2(x)
x=self.bn2(x,training=training)
x=tf.nn.leaky_relu(x)
x=self.conv3(x)
x=tf.tanh(x)
return x
# 这个例子告诉我们,在 keras 的自定义层中直接在 call 中调用 keras 内置层是可以有可训练变量的。
class Discriminator(keras.Model):
def __init__(self):
super(Discriminator,self).__init__()
self.conv1=keras.layers.Conv2D(64,5,3,'valid')
self.conv2=keras.layers.Conv2D(128,5,3,'valid')
self.bn2=keras.layers.BatchNormalization()
self.conv3=keras.layers.BatchNormalization()
self.bn3=keras.layers.BatchNormalization()
self.flattn=keras.layers.Flatten()
self.fc=keras.layers.Dense(1)
def call(self, inputs, training=None, mask=None):
x=self.conv1(inputs)
x=tf.nn.leaky_relu(x)
x=self.conv2(x)
x=self.bn2(x,training=training)
x=tf.nn.leaky_relu(x)
x=self.conv3(x)
x=self.bn3(x,training=training)
x=tf.nn.leaky_relu(x)
x=self.flatten(x)
logits=self.fc(x)
return logits
Tip: keras 两种初始化模型的方法
原来我只会第一种初始化模型的方法,这份源码教会了我第二种方法
# 方法1:从 Input 开始指定前向过程,最后根据输入和输出来建立模型
inputs = tf.keras.Input(shape=(3,))
x = tf.keras.layers.Dense(4, activation=tf.nn.relu)(inputs)
outputs = tf.keras.layers.Dense(5, activation=tf.nn.softmax)(x)
model = tf.keras.Model(inputs=inputs, outputs=outputs)
# 方法2:构建 Model 的子类,__init__中定义层的实现,call函数中实现前向过程
class MyModel(tf.keras.Model):
def __init__(self):
super(MyModel, self).__init__()
self.dense1 = tf.keras.layers.Dense(4, activation=tf.nn.relu)
self.dense2 = tf.keras.layers.Dense(5, activation=tf.nn.softmax)
def call(self, inputs):
x = self.dense1(inputs)
output = self.dense2(2)
return output
model = MyModel() # 通过初始化整个类来初始化该模型
def main():
d=Discrimination()
g=Generator()
x=tf.random.normal([2,64,64,3])
z=tf.random.normal([2,100]) # 随机初始化两个一百维度的 z
prob=d(x)
print(prob.shape) # 应该的形状为[-1,1]
x_hat=g(z)
print(x_hat.shape) # 应该的形状为[-1,64,64,3]
if __name__=='__main__': # 这样写 在作为一个模块导入其他文件时 main() 并不会被直接执行因为在其他文件调用此文件时,__name__!='__main__',而如果直接运行本模块的化,可以直接运行 main 函数此时 __name__=='main'
main()
gan_train.py
import os
import numpy as np
import tensorflow as tf
from tensorflow import keras
form PIL import Image # PIL 是图像处理标准库
import glob # glob 是操作文件的相关模块
from gan import Generator, Discriminator
from dataset import make_anime_dataset
def save_result(val_out, val_block_size, image_path, color_mode):
# 保存结果
def prepocess(img):
img = ((img+1.0)*127.5).astype(np.uint8) # 将输出的张量复原为一张图像
return img
preprocesed = prepocess(val_out)
final_image = np.array([])
single_row = np.array([])
for b in range(val_out.shape[0]):
if single_row.size == 0: # 如果 single_row 中没有图片
single_row = preprocesed[b,:,:,:] # 取第 b 张图片
else: # 如果 single_row 中已经有图片了
single_row = np.concatenate((single_row,preprocesed[b,:,:,:]),axis=1) # 将图片和之前的图片沿着 axis=1轴 合并成一张图片
if (b+1) % val_block_size == 0: # 如果这是最后一张图片
if final_image.size == 0:
final_image = single_row # 将 final_image 令为最后一张图片
else:
final_image = np.concatenate((final_image, single_row), axis=0) # 如果 final_image 中有图片就再在其中添加 single_row
single_row = np.array([]) # 重置 single_row
if final_image.shape[2]==1:
final_image=np.squeeze(final_image,axis=2)
Image.fromarray(final_image).save(image_path)
def celoss_ones(logits): # logits shape (None,1)
# 与全 1 张量之间的交叉熵
loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=logits,
labels=tf.ones_like(logits)) # tf.zeros_like/tf.ones_like 新建一个与给定 tensor 类型大小一致的 tensor 所有元素为 0/1 .
return tf.reduce_mean(loss)
def celoss_zeros(logits): # logits shape (None,1)
# 与全 0 张量之间的交叉熵
loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=logits,
labels=tf.zeros_like(logits)) # tf.zeros_like/tf.ones_like 新建一个与给定 tensor 类型大小一致的 tensor 所有元素为 0/1 .
return tf.reduce_mean(loss)
def d_loss_fn(generator, discriminator, batch_z, batch_x, is_training):
# discriminator 的损失函数
fake_image = generator(batch_z, is_training)
d_fake_logits = discriminator(fake_image, is_training)
d_real_logits = discriminator(batch_x, is_training)
d_loss_real = celoss_ones(d_real_logits)
d_loss_fake = celoss_zeros(d_fake_logits)
loss = d_loss_fake + d_loss_real # 注意 fake 和 real 是等比例输入的,之后 discriminator 的 loss 应该将两部分加起来。
return loss
def g_loss_fn(generator, discriminator, batch_z, is_training):
# generator 的损失函数
fake_image = generator(batch_z, istraining) # 相当于一个 layer (batch_z 是该 layer 的输入)
d_fake_logits = discriminator(fake_image,is_training)
loss = celoss_ones(d_fake_logits)
return loss
def main():
tf.random.set_seed(22) # 设置随机变量种子
np.random.seed(22)
os.environ['TF_CPP_MIN_LOG_LEVEL']='2' # ERROR 及其以上的错误才报
assert tf.__version__.startswith('2.') # 一定要是 tensorflow 2.0
Tip: assert
assert expression # 声称,如果不是的话就抛出错误
# 等价于:
if not expression:
raise AssertionError
def main():
tf.random.set_seed(22) # 设置随机变量种子
np.random.seed(22)
os.environ['TF_CPP_MIN_LOG_LEVEL']='2' # ERROR 及其以上的错误才报
assert tf.__version__.startswith('2.') # 一定要是 tensorflow 2.
z_dim = 100
epochs = 3000000
batch_size = 512
learning_rate = 0.002
is_training = True
img_path=glob.glob(r'E:\python_pro\TF2.0\GAN\faces\*.jpg') #图片路径
dataset, img_shape, _ = make_anime_dataset(img_path, batch_size)
print(dataset, img_shape)
dataset = dataset.repeat()
db_iter = iter(dataset) # iter(object)函数用来生成迭代器,其中 object 为支持迭代的集合对象,返回一个迭代器对象
generator = Generator()
generator.build(input_shape=(None, z_dim))
discriminator = Discriminator()
discriminator.build(input_shape=(None, 64, 64, 3))
g_optimizer = tf.optimizers.Adam(learning_rate=learning_rate, beta_1=0.5)
d_optimizer = tf.optimizers.Adam(learning_rate=learning_rate, beta_1=0.5)
for epoch in range(epochs): # 每一个训练周期
batch_z = tf.random.uniform([batch_size,z_dim],minval=-1.,maxval=1.)
batch_x = next(db_iter) # 每个周期只会取其中的一个小 batch 来训练
Tip:以前我每个 epoch 选取 batch 的写法
for epoch in range(epochs):
for data, label in dataset:
没有什么太大的区别,它的一个周期取一个 batch 我的一个周期 遍历整个数据集。
# 发现一个新大陆,所有的Dataset 可以用 iter(Dataset) 生成迭代对象,使用 next 取出。相当于一个栈式结构,每次取出一个 tf.Tensor.
db_iter=iter(dataset)
for epoch in range(epochs):
batch_x=next(db_iter)
with if.GradientTape() as tape:
d_loss = d_loss_fn(generator, discriminator, batch_z, batch_x, is_training)
grads = tape.gradient(d_loss,discriminator.trainable_variables)
d_optimizer.apply_gradients(zip(grads, discriminator.trainable_variables))
with tf.GradientTape() as tape:
g_loss = g_loss_fn(generator, discriminator, batch_z, is_training)
grads = tape.gradient(g_loss,generator.trainable_variables)
g_optimizer.apply_gradients(zip(grads, generator.trainable_variables))
if epoch % 100 == 0:
print(epoch,'d-loss:',float(d_loss),'g-loss',float(g_loss))
z = tf.random.uniform([100,z_dim])
fake_image = generator(z,training=False)
img_path = os.path.join('images','gan-%d.png'%epoch)
save_result(fake_image.numpy(),10,img_path,color_mode='P')
if __name__=='__main__':
main()
学学别人写的源码也是挺好的。
任务三:基于Gans完成一个汽车信息管理系统
根据gans快速开发平台给出的示例,开始完成这个汽车信息管理系统
首先准备好初始化的sql,初始化到数据库中
DROP TABLE IF EXISTS `car`;
CREATE TABLE `car` (
`car_id` bigint(20) NOT NULL COMMENT '车辆id',
`car_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '车辆名称',
`car_type` tinyint(4) NULL DEFAULT NULL COMMENT '车辆种类:1-轿车,2-货车',
`car_color` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '车辆颜色',
`car_price` decimal(20, 2) NULL DEFAULT NULL COMMENT '车辆价格',
`manufacturer` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '制造商',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
`update_user` bigint(20) NULL DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`car_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '车辆管理' ROW_FORMAT = Dynamic;
INSERT INTO `car` VALUES (1339554696976782409, '奥迪A6', 1, '白色', 300000.00, '奥迪公司', '2021-02-06 17:06:33', NULL, NULL, NULL);
INSERT INTO `car` VALUES (1339554696976782410, '一汽解放', 2, '黑色', 200000.00, '一汽公司', '2021-02-06 17:06:33', NULL, NULL, NULL);
因此,我们可以在前端顶层中得到一个业务应用APP里面的车辆管理系统
并且得到了六个文件
其中包括
三个jar包
三个前端页面
然后是后端
这是后端目录
项目内容:
CarController
package cn.stylefeng.guns.modular.business.controller;
import cn.stylefeng.guns.modular.business.pojo.CarRequest;
import cn.stylefeng.guns.modular.business.service.CarService;
import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData;
import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData;
import cn.stylefeng.roses.kernel.scanner.api.annotation.ApiResource;
import cn.stylefeng.roses.kernel.scanner.api.annotation.GetResource;
import cn.stylefeng.roses.kernel.scanner.api.annotation.PostResource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 车辆管理控制器
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@RestController
@ApiResource(name = "车辆管理")
public class CarController {
@Resource
private CarService carService;
/**
* 添加车辆
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@PostResource(name = "添加车辆", path = "/car/add")
public ResponseData add(@RequestBody @Validated(CarRequest.add.class) CarRequest carRequest) {
carService.add(carRequest);
return new SuccessResponseData();
}
/**
* 删除车辆
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@PostResource(name = "删除车辆", path = "/car/delete")
public ResponseData delete(@RequestBody @Validated(CarRequest.delete.class) CarRequest carRequest) {
carService.del(carRequest);
return new SuccessResponseData();
}
/**
* 编辑车辆
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@PostResource(name = "编辑车辆", path = "/car/edit")
public ResponseData edit(@RequestBody @Validated(CarRequest.edit.class) CarRequest carRequest) {
carService.edit(carRequest);
return new SuccessResponseData();
}
/**
* 查看车辆详情
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@GetResource(name = "查看车辆详情", path = "/car/detail")
public ResponseData detail(@Validated(CarRequest.detail.class) CarRequest carRequest) {
return new SuccessResponseData(carService.detail(carRequest));
}
/**
* 查询车辆列表
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@GetResource(name = "获取车辆列表", path = "/car/findList")
public ResponseData list(CarRequest carRequest) {
return new SuccessResponseData(carService.findList(carRequest));
}
/**
* 分页查询车辆列表
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@GetResource(name = "分页查询", path = "/car/findPage")
public ResponseData page(CarRequest carRequest) {
return new SuccessResponseData(carService.findPage(carRequest));
}
}
CarViewController
package cn.stylefeng.guns.modular.business.controller;
import cn.stylefeng.roses.kernel.scanner.api.annotation.ApiResource;
import cn.stylefeng.roses.kernel.scanner.api.annotation.GetResource;
import org.springframework.stereotype.Controller;
/**
* 车辆管理界面
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@Controller
@ApiResource(name = "车辆管理界面")
public class CarViewController {
/**
* 车辆管理首页
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@GetResource(name = "车辆管理首页", path = "/view/car")
public String carIndex() {
return "/modular/business/car/car.html";
}
/**
* 车辆管理-新增
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@GetResource(name = "车辆管理-新增", path = "/view/car/add")
public String carAdd() {
return "/modular/business/car/car_add.html";
}
/**
* 车辆管理-编辑
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@GetResource(name = "车辆管理-编辑", path = "/view/car/edit")
public String carEdit() {
return "/modular/business/car/car_edit.html";
}
}
Car
package cn.stylefeng.guns.modular.business.entity;
import cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
/**
* 车辆管理
*
* @author stylefeng
* @date 2020/3/25 14:00
*/
@TableName("car")
@EqualsAndHashCode(callSuper = true)
@Data
public class Car extends BaseEntity {
/**
* 车辆id
*/
@TableId("car_id")
private Long carId;
/**
* 车辆名称
*/
@TableField("car_name")
private String carName;
/**
* 车辆种类:1-轿车,2-货车
*/
@TableField("car_type")
private Integer carType;
/**
* 车辆颜色
*/
@TableField("car_color")
private String carColor;
/**
* 车辆价格
*/
@TableField("car_price")
private BigDecimal carPrice;
/**
* 制造商
*/
@TableField("manufacturer")
private String manufacturer;
}
CarExceptionEnum
package cn.stylefeng.guns.modular.business.exception;
import cn.stylefeng.guns.core.consts.ProjectConstants;
import cn.stylefeng.roses.kernel.rule.constants.RuleConstants;
import cn.stylefeng.roses.kernel.rule.exception.AbstractExceptionEnum;
import lombok.Getter;
/**
* 车辆异常枚举
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
@Getter
public enum CarExceptionEnum implements AbstractExceptionEnum {
/**
* 车辆不存在
*/
CAR_NOT_EXISTED(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + ProjectConstants.BUSINESS_EXCEPTION_STEP_CODE + "01", "车辆不存在");
/**
* 错误编码
*/
private final String errorCode;
/**
* 提示用户信息
*/
private final String userTip;
CarExceptionEnum(String errorCode, String userTip) {
this.errorCode = errorCode;
this.userTip = userTip;
}
}
CarMapper.xml
<?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="cn.stylefeng.guns.modular.business.mapper.CarMapper">
</mapper>
CarMapper
package cn.stylefeng.guns.modular.business.mapper;
import cn.stylefeng.guns.modular.business.entity.Car;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 车辆管理数据层
*
* @author stylefeng
* @date 2020/3/25 14:00
*/
public interface CarMapper extends BaseMapper<Car> {
}
CarRequest
package cn.stylefeng.guns.modular.business.pojo;
import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* 车辆管理请求
*
* @author stylefeng
* @date 2020/3/25 14:00
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CarRequest extends BaseRequest {
/**
* 车辆id
*/
@NotNull(message = "车辆id不能为空", groups = {edit.class, delete.class, detail.class})
private Long carId;
/**
* 车辆名称
*/
@NotBlank(message = "车辆名称不能为空", groups = {add.class, edit.class})
private String carName;
/**
* 车辆种类:1-轿车,2-货车
*/
@NotNull(message = "车辆种类不能为空", groups = {add.class, edit.class})
private Integer carType;
/**
* 车辆颜色
*/
private String carColor;
/**
* 车辆价格
*/
private BigDecimal carPrice;
/**
* 制造商
*/
private String manufacturer;
}
CarServiceImpl
package cn.stylefeng.guns.modular.business.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.stylefeng.guns.core.exception.BusinessException;
import cn.stylefeng.guns.modular.business.entity.Car;
import cn.stylefeng.guns.modular.business.exception.CarExceptionEnum;
import cn.stylefeng.guns.modular.business.mapper.CarMapper;
import cn.stylefeng.guns.modular.business.pojo.CarRequest;
import cn.stylefeng.guns.modular.business.service.CarService;
import cn.stylefeng.roses.kernel.db.api.factory.PageFactory;
import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory;
import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 车辆管理业务实现层
*
* @author stylefeng
* @date 2020/3/25 14:00
*/
@Service
public class CarServiceImpl extends ServiceImpl<CarMapper, Car> implements CarService {
@Override
public void add(CarRequest carRequest) {
Car car = new Car();
BeanUtil.copyProperties(carRequest, car);
this.save(car);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void del(CarRequest carRequest) {
Car car = this.queryCar(carRequest);
this.removeById(car.getCarId());
}
@Override
public void edit(CarRequest carRequest) {
Car car = this.queryCar(carRequest);
BeanUtil.copyProperties(carRequest, car);
this.updateById(car);
}
@Override
public Car detail(CarRequest carRequest) {
return this.queryCar(carRequest);
}
@Override
public PageResult<Car> findPage(CarRequest carRequest) {
LambdaQueryWrapper<Car> wrapper = createWrapper(carRequest);
Page<Car> sysRolePage = this.page(PageFactory.defaultPage(), wrapper);
return PageResultFactory.createPageResult(sysRolePage);
}
@Override
public List<Car> findList(CarRequest carRequest) {
LambdaQueryWrapper<Car> wrapper = this.createWrapper(carRequest);
return this.list(wrapper);
}
/**
* 获取车辆信息
*
* @author stylefeng
* @date 2020/3/25 14:00
*/
private Car queryCar(CarRequest carRequest) {
Car car = this.getById(carRequest.getCarId());
if (ObjectUtil.isEmpty(car)) {
throw new BusinessException(CarExceptionEnum.CAR_NOT_EXISTED);
}
return car;
}
/**
* 创建查询wrapper
*
* @author fengshuonan
* @date 2020/3/25 14:00
*/
private LambdaQueryWrapper<Car> createWrapper(CarRequest carRequest) {
LambdaQueryWrapper<Car> queryWrapper = new LambdaQueryWrapper<>();
String carName = carRequest.getCarName();
queryWrapper.like(ObjectUtil.isNotEmpty(carName), Car::getCarName, carName);
// 根据时间倒序排列
queryWrapper.orderByDesc(Car::getCreateTime);
return queryWrapper;
}
}
CarService
package cn.stylefeng.guns.modular.business.service;
import cn.stylefeng.guns.modular.business.entity.Car;
import cn.stylefeng.guns.modular.business.pojo.CarRequest;
import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* 车辆管理业务层
*
* @author stylefeng
* @date 2020/3/25 14:00
*/
public interface CarService extends IService<Car> {
/**
* 添加车辆
*
* @param carRequest 添加参数
* @author stylefeng
* @date 2020/3/25 14:00
*/
void add(CarRequest carRequest);
/**
* 删除车辆
*
* @param carRequest 删除参数
* @author stylefeng
* @date 2020/3/25 14:00
*/
void del(CarRequest carRequest);
/**
* 编辑车辆
*
* @param carRequest 编辑参数
* @author stylefeng
* @date 2020/3/25 14:00
*/
void edit(CarRequest carRequest);
/**
* 查看车辆详情
*
* @param carRequest 查看参数
* @author stylefeng
* @date 2020/3/25 14:00
*/
Car detail(CarRequest carRequest);
/**
* 分页查询车辆
*
* @param carRequest 查询参数
* @return 查询分页结果
* @author stylefeng
* @date 2020/3/25 14:00
*/
PageResult<Car> findPage(CarRequest carRequest);
/**
* 查询所有车辆
*
* @param carRequest 查询参数
* @return 查询分页结果
* @author stylefeng
* @date 2020/3/25 14:00
*/
List<Car> findList(CarRequest carRequest);
}
因此我们可以得到这样的一个前端页面
三、实验总结
Guns的知识复杂泷乱,不太能一时间学会,并且触类旁通,只能慢慢的一点点的跟老师给的教程做出了成果,虽然没有独立自主完成,成就感还是有的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2020-12-10 2020/12/10 刘一辰的JAVA随笔