我们看一下文本编辑器的案例:http://localhost:8091/easy-ui/easy-13-kindeditor.
图片上传不可使用本地的图片,主要是因为案例没有上传的实现业务代码。这只是演示一下效果
我们看一下表的设计:商品详情表为tb_item_desc.。他的item_id正是商品表的主键id,在此作为主键和外键。
说明:商品表与商品描述信息有关联关系.典型一对一.并且主键的值相同.
思考:为什么需要准备2张表.而不是使用tb_item进行统一维护
原因:一般电商网站查询时首选展现的是商品信息.如果用户选择某个商品时才会查询商品详情信息.这样执行的效率高.
根据上述的表,设计pojo对象:
声明一下:
1. POJO(Plain Ordinary Java Object)简单无规则java对象。是作为对应数据源对象而存在的,POJO 里的属性只是对应着数据库的数据字段的别名。并提供get和set方法。
2. VO(Value Object) 值对象。主要用在前端数据和控件的绑定操作中,它是根据前端的需求而创建的对象。那它的作用就是帮助 controller 层将从 DataSource 里拿到数据后进一步整理后转为页面所需要的值。
进入正题,在jt-common 中创建ItemDesc.java
ItemDesc:
@Data
@Accessors(chain = true)
@TableName("tb_item_desc")
public class ItemDesc extends BasePojo{
/**
* id信息与商品Id号一致,故不可设置为自增长
*/
@TableId
private Long itemId;
/**
* 详情内容
*/
private String itemDesc;
}
ItemController:因为商品和详情是一起保存的,故我们只需在原有的商品保存方法上加上参数即可。
ItemController:(上一教程中,我们创建了一个全局异常处理机制,故在这我们无需再单独捕获异常)
/**
* 实现商品数据新增
* 改进方法:定义全局异常处理机制
* 二:实现商品描述的新增
*/
@RequestMapping("/save")
public SysResult saveItem(Item item,ItemDesc itemDesc) {
itemService.saveItem(item,itemDesc);
return SysResult.success();
}
/**
* 实现商品信息修改
*/
@RequestMapping("/update")
public SysResult updateItem(Item item, ItemDesc itemDesc) {
itemService.updateItem(item,itemDesc);
return SysResult.success();
}
ItemServiceImpl:将ItemDescMapper.java和ItemDescMapper.xml补上即可。这里使用还是mybatis-plus的insert方法。
@Transactional //控制事务
@Override
public void saveItem(Item item,ItemDesc itemDesc) {
item.setStatus(1) //表示正常
.setCreated(new Date())
.setUpdated(item.getCreated());
itemMapper.insert(item);
//只有Item入库之后,才能获取主键id值
//能否实现数据入库之后,将主键Id自动返回/封装
//完成商品详情入库
itemDesc.setItemId(item.getId())
.setCreated(item.getCreated())
.setUpdated(item.getCreated());
itemDescMapper.insert(itemDesc);
}
@Transactional
@Override
public void updateItem(Item item,ItemDesc itemDesc) {
item.setUpdated(new Date());
itemMapper.updateById(item);
//ItemDesc itemDesc属性/itemId/updated不为null
itemDesc.setItemId(item.getId())
.setUpdated(item.getUpdated());
//根据主键itemId更新数据!!!
itemDescMapper.updateById(itemDesc);
}
我们来操作一下:重启一下服务
新增商品:
图片本地上传,一会讲解。
我们编辑一下新增的商品,F12,查看请求路径
我们看到了详情的获取是单独获取的。分析一下请求路径:
/item/query/item/desc/1474391967 。我们看到了最后是一串数字。很像id。去看一下源码
查看页面JS
$.getJSON('/item/query/item/desc/'+data.id,function(_data){
if(_data.status == 200){
//UM.getEditor('itemeEditDescEditor').setContent(_data.data.itemDesc, false);
itemEditEditor.html(_data.data.itemDesc);
}
});
那我们在去数据库看看
先看tb_item
再看tb_item_desc:
我们可以看到,他是将id值作为链接进行请求。却不是参数的形式。OK,我们去设计一下controller
ItemController:
/**
* 根据商品详情信息获取服务器数据
*/
@RequestMapping("/query/item/desc/{itemId}")
public SysResult findItemDescById(@PathVariable Long itemId) {
ItemDesc itemDesc =itemService.findItemDescById(itemId);
return SysResult.success(itemDesc);
}
可以看到,spring他提供了这样的形式。简单说一下:
url中采用动态绑定的形式表示。之后在方法中指定的参数通过PathVariable来将请求的url中的参数绑定到方法参数,这里在不指定PathVariable注解的具体值时按照请求路径中的动态变量顺序与注解顺序一致即可注入 ,如果想指定注入,那么指定PathVariable注解的值与动态变量的名一致即可特定注入。
所以,注解中的 {itemId} 即将最后的参数设置为了动态获取,通过注解 @PathVariable就能将注解的itemId与参数的itemId绑定在一起。若参数跟注解的名字不一样时,需要指明一下 @PathVariable(“itemId”)。
动态获取其实也是可以有多个的,伪代码为:
@RequestMapping("/query/item/{aa}/{bbb}")
public string findItemDescById(@PathVariable Long aa,@PathVariable Long bbb) {
}
这样使用的方式也为我们的程序带来了一定的安全性,这样黑客无法获取到真实的请求路径。
ItemServiceImpl:
@Override
public ItemDesc findItemDescById(Long itemId) {
return itemDescMapper.selectById(itemId);
}
业务分析:
找到源码:
可以看到删除是可以批量删除的。
ItemController:
/**
* 实现删除
*/
@RequestMapping("/delete")
public SysResult delteItems(Long[] ids) {
itemService.deleteItems(ids);
return SysResult.success();
}
ItemServiceImpl:
/**
* 利用xml文件实现删除
* 同时删除2张表数据库
*/
@Transactional
@Override
public void deleteItems(Long[] ids) {
List<Long> idsList = Arrays.asList(ids);
itemMapper.deleteBatchIds(idsList);
itemDescMapper.deleteItems(ids);
}
ItemMapper:
void deleteItems(Long[] ids);
ItemMapper.xml
<!--
collection取值规范
如果参数为map类型 collection="key"
参数为数组 collection="array"
参数为list集合 collection="list"
-->
<delete id="deleteItems">
delete from tb_item_desc where
item_id in (
<foreach collection="array" item="id" separator=",">
#{id}
</foreach>
)
</delete>
查看源码:
common.js
原来链接在这里
我们看到它使用的kindEditor,查看开发文档 http://kindeditor.net/docs/upload.html
可以看到它的返回json结果为:
//成功时
{
"error" : 0,
"url" : "http://www.example.com/path/to/file.ext"
}
//失败时
{
"error" : 1,
"message" : "错误信息"
}
根据上述格式创建vo对象:
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class EasyUIImage {
/**
* 1表示文件上传失败
*/
private Integer error=0;
/**
* 图片访问地址
*/
private String url;
}
FileController
@Controller
public class FileController {
@Autowired
private FileService fileService;
@RequestMapping("/pic/upload")
@ResponseBody
public EasyUIImage uploadFile(MultipartFile uploadFile) {
return fileService.uploadFile(uploadFile);
}
FileServiceImpl:
@Service
@PropertySource("classpath:/properties/image.properties")
public class FileServiceImpl implements FileService {
@Value("${image.localFileDir}")
private String localFileDir;
@Value("${image.urlPath}") //http://image.jt.com/
private String urlPath;
/**
* 1.判断文件是否为图片 jpg|png|gif
* 2.防止恶意程序 判断图片固有属性 宽度和高度
* 3.将图片分目录存储 按照时间进行存储 yyyy/MM/dd
* 4.解决文件重名问题 UUID
*/
@Override
public EasyUIImage uploadFile(MultipartFile uploadFile) {
EasyUIImage uiImage = new EasyUIImage();
//1.获取图片名称 abc.jpg ABC.JPG
String fileName = uploadFile.getOriginalFilename();
//应该将用户输入内容统一转化为小写
fileName = fileName.toLowerCase();
//2.利用正则表达式判断数据
if(!fileName.matches("^.+\\.(jpg|png|gif)$")) {
uiImage.setError(1);//表示不是正经图片
return uiImage;
}
//3.获取图片的宽度和高度
try {
//4.以时间格式进行数据存储 yyyy/MM/dd
String dateDir =
new SimpleDateFormat("yyyy/MM/dd")
.format(new Date());
//准备文件上传路径D:/jt-image/yyyy/MM/dd
String localdir = localFileDir + dateDir;
File dirFile = new File(localdir);
if(!dirFile.exists()) {
//如果文件路径不存在,则创建文件夹
dirFile.mkdirs();
}
/**
* 5.重新生成图片名称
* 5.1 生成UUID xxxx-xxx
* 5.2将-替换""空串
* 5.3获取真实图片的后缀 .jpg
* 5.4生成真实的图片名称 uuid.jpg
*/
String uuid = UUID.randomUUID()
.toString()
.replace("-","");
//abc.jpg 从后向前查找第一个.的位置
//从该位置向后截取数据.
String fileType =
fileName.substring(fileName.lastIndexOf("."));
String realFileName =uuid + fileType;
/**
* 6.实现文件上传
* 6.1准备文件全路径
* D:\jt-image\2019\08\02\ uuid.jpg
* 6.2 实现文件上传
*/
//D:/jt-image/yyyy/MM/dd/uuid.jpg
String realPath = localdir+ "/" + realFileName;
File realFile = new File(realPath) ;
uploadFile.transferTo(realFile);
System.out.println("文件上传成功!!!!!!!!");
//http://image.jt.com/yyyy/MM/dd/uuid.jpg
String url = urlPath + dateDir + "/" + realFileName;
uiImage.setUrl(url);
} catch (Exception e) {
e.printStackTrace();
uiImage.setError(1);//程序出错.上传终止
}
return uiImage;
}
}
为了实现路径动态获取编辑image.properties文件.没有创建即可
image.localFileDir=D:/jt-image/
image.urlPath=http://image.jt.com/
上面用到了 image.urlPath=http://image.jt.com/ 这个配置。说明一下:
通过代码我们发现返回的URL格式是 http://image.jt.com/yyyy/MM/dd/uuid.jpg,并不是本地路径。这里正是运用了nginx技术中的反向代理。下面就介绍一下nginx
关于nginx的安装和简单使用,请看文章 https://blog.csdn.net/qq_37216403/article/details/105059429.
此文章只应用到了域名的代理。而我们这次使用它的反向代理。
先启动nginx。nginx默认的端口是80.直接访问localhost。成功了会出现下面的界面
若发现80端口占用的话,有俩种方式
接下来,修改HOSTS文件
说明:在本机环境中测试时,可以认为的修改域名与IP的映射关系.
路径 C:\Windows\System32\drivers\etc修改hosts文件
在里面添加 域名映射。其他的映射是其他模块时候用到的。往后的教程就能看到。先加上再说。
#IP地址 域名
127.0.0.1 image.jt.com
127.0.0.1 manage.jt.com
127.0.0.1 www.jt.com
127.0.0.1 sso.jt.com
127.0.0.1 cart.jt.com
127.0.0.1 order.jt.com
在nginx.conf添加
server{
listen 80;
server_name manage.jt.com;
location / {
proxy_pass http://localhost:8091;
}
}
修改完成之后,将nginx服务器重启
nginx -s reload
上面实现了域名的映射。那图片的地址代理又该如何实现呢?
在nginx.conf中添加
#图片服务管理
server {
listen 80;
server_name image.jt.com;
#反向代理路径配置
location / {
# root 关键字 代表路径映射本机的地址
root D:\jt-image;
}
}
重启nginx,试验一下
编辑上面添加的新的商品
在描述里添加一个本地图片
确定
在图片上右击鼠标,查看属性
我们看到地址 http://image.jt.com/2020/04/08/ce13c917ebf0491b9e18ed88fd825178.png
再看一下本地的
可以看到图片地址反向代理成功。这也是实现了跟京东和淘宝商品图片地址一样的效果。
但我发现了一个问题,或者点击富文本编辑器的批量图片上传。(其实这俩是一个)
经过检查,其实上传成功了,这是浏览器的问题。换其他浏览器就没问题。可惜我的前端知识不是很好。所以就忽略吧。
https://github.com/lmy1965673628/jingtao.git.
我们上面实现了对商品添加功能的进一步完善。也实现了图片的上传和nginx的反向代理。下一节将具体介绍nginx的负载均衡的配置,策略和使用