好多天都没更了,这两天在老师的帮助下从零到一完成了一个商城购物后台管理的项目,今天刚刚进行项目发布,完结项目。下面来给大家分享一下项目步骤及知识点,顺带再次自己复习一遍。
由于主攻后端开发的原因,前端页面的搭建只做了解和简单的学习,没有深入去学习,能看懂逻辑即可。
一、模块总览
1.用户登录
用户登录请求数据库进行查询返回标识进行判断是否登陆成功
2.用户管理---用户列表
用户信息
功能属性:id、用户名、电话、邮箱、用户状态、操作(修改、删除)、查找用户、新增用户、翻页跳转功能
3.商品管理---商品分类
商品分类
功能属性:id、分类名称(包含一级分类、二级分类、三级分类)、商品分类状态、标识分类等级、操作(修改、删除)、新增分类(分类名称、父级分类名称)
3.商品管理---商品列表
商品信息
功能属性:id、商品标题、卖点信息、价格、数量、商品状态、操作(修改、删除)、添加商品【(基本信息---商品标题、商品买点、商品价格、商品数量、商品分类信息)、(商品图片---上传图片)、(商品详情---富文本编辑器进行商品的详细描述)】
4.权限管理---shiro框架正在努力学习中
二、用户登录模块
1.数据库的设计
字段:id、username、password、phone、email、status、created、updated
原始数据如图:

密码进行md5算法加密
创建时间以及表的更新时间自动更新
2.实体类的创建
@TableName("user")
@Data
@Accessors(chain = true)
public class User extends BasePojo{
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String phone;
private String email;
private Boolean status;
@TableField(exist = false) //该属性不存在
private Role role; //定义role角色数据
}
@Data
@Accessors(chain=true)
public class BasePojo implements Serializable{
@TableField(fill = FieldFill.INSERT)
private Date created; //表示入库时需要赋值
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updated; //表示入库/更新时赋值.
}
3.返回前端页面参数类的设计实现
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SysResult implements Serializable {
private Integer status;//200业务成功 201业务失败
private String mag;//服务器的提示信息
private Object data;//封装后台返回值
public static SysResult fail(){
return new SysResult(201, "业务执行失败",null);
}
public static SysResult fail2(){
return new SysResult(202, "业务执行失败",null);
}
public static SysResult success(){
return new SysResult(200, "业务执行成功", null);
}
public static SysResult success(Object data){
return new SysResult(200, "业务执行成功", data);
}
public static SysResult success(String msg,Object data){
return new SysResult(200, msg, data);
}
}
4.控制层的实现
4.1登录---controller
@PostMapping("/login")
public SysResult login(@RequestBody User user){
String token = userService.login(user);
if(token == null || token.length()==0){
return SysResult.fail();
}
return SysResult.success(token);
}
思路:去数据库进行数据比对,相同则返回token,否则返回null
4.2登录---service
@Override
public String login(User user) {
//获取明文
String password=user.getPassword();
//加密处理
String md5= DigestUtils.md5DigestAsHex(password.getBytes());
user.setPassword(md5);
//获取数据库对象
User userDB = userMapper.selectOne(new QueryWrapper<User>(user));
//判断登录是否正常
if(userDB==null){
return null;
}
//使用UUID动态生成TOKEN,根据当前毫秒数+随机数利用hash算法生成
//几乎保证不重复2^128
String token = UUID.randomUUID().toString().replace("-", "");
return token;
}
思路:获取对象密码、进行MD5加密之后再次set密码在数据库进行查找,如果没有返回值则登录失败返回null,否则用UUID生成token进行返回
知识点:
- 如果数据库里不存在,而实体类中需要额外添加属性应该加注解 @TableField(exist = false)
- 层架结构新增VO层:封装前后端交互数据的媒介
- MD5:MD5信息摘要算法,一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值,用于确保信息传输完整一致,.理论上MD5不可以被破解的. 只能由明文加密为密文. 不可以反向编译,但是可以用穷举法进行某种意义上的破解
- UUID:使用UUID动态生成TOKEN,根据当前时间毫秒数+随机数利用hash算法生成,几乎可以保证不重复.(有2^128的概率hash碰撞)
三、用户列表模块
1.返回前端页面参数类的设计实现
@Data
@Accessors(chain = true)
public class PageResult implements Serializable {
private String query; 用户查询的数据
private Integer pageNum; 查询页数
private Integer pageSize; 查询条数
private Long total; 查询总记录数
private Object rows; 分页查询的结果
}
2.用户页面展现控制层的实现
2.1用户页面展现---Controller
@GetMapping("list")
public SysResult getUserList(PageResult pageResult){
pageResult=userService.getUserList(pageResult);
return SysResult.success(pageResult);
}
思路:前端传入页数、每页多少条记录、查询关键字,我们添加查询总记录数和分页查询结果进行返回
2.2用户页面展现---service
方法一:手写sql
在mapper里定义抽象方法,在service里进行调用
public interface UserMapper extends BaseMapper<User> {
//只适用于简单sql,和映射文件二选一a
@Select("select * from user limit #{start},#{size}")
List<User> findListByPage(int start, int size);
}
@Override
public PageResult getUserList(PageResult pageResult) {
//1.获取总记录数
long total = userMapper.selectCount(null);
//2.获取分页结果
int size = pageResult.getPageSize();
int start =(pageResult.getPageNum()-1)* size;
List<User> userList=userMapper.findListByPage(start,size);
pageResult.setTotal(total).setRows(userList);
return pageResult;
}
思路:在mapper里定义分页sql语句,在service里通过selectCount获取到总记录数,获取记录条数和页数,调用mapper里的方法传入获取集合分别进行set封装并返回。
方法二:MP实现
@Override
@Transactional
public PageResult getUserList(PageResult pageResult) {
Page<User> page = new Page<>(pageResult.getPageNum(), pageResult.getPageSize());
boolean x=StringUtils.hasLength(pageResult.getQuery());
page = userMapper.selectPage(page, new QueryWrapper<User>().like(x, "username", pageResult.getQuery()));
pageResult.setTotal(page.getTotal()).setRows(page.getRecords());
return pageResult;
}
思路:首先进行selectPage分页查询,发现需要两个参数,一个page分页对象,一个条件构造器,这时我们创建分页对象page并传入两个参数,一个页数,一个每页的记录条数。再new条件构造器,这是涉及到了模糊查询。所以在定义一个boolean变量,判断是否传入了查询条件,没有则不执行直接进行分页,最后进行赋值操作并返回
2.3编辑配置类---config
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));
return interceptor;
}
}
知识点:
在Mapper里手写sql只适用于简单sql,和映射文件二选一,且注解有select、delete、update、insert
@Transactional注解在更改数据库内容时需要添加。如果后台服务器发生问题,则数据信息应该回滚,而不是提交操作。默认发生运行时异常错误的话会发生事务回滚 。发生编译异常(检查异常),默认规则事务不回滚 。属性说明:noRollbackFor={异常}遇到某种异常不回滚 rollbackFor={}遇到某种异常回滚 。作用: 可以保证数据的 /持久性(永久性)/一致性/隔离性/原子性
API:StringUtils.hasLength() 判断有没有值,有就true,没有就false
编辑配置类:MybatisPlus在执行分页操作时,会被该拦截器拦截,拦截器的作用 动态拼接where条件。主要语句new PaginationInnerInterceptor(DbType.MARIADB));类型是自己所用的数据库,这里是MariaDB
3.用户状态修改
3.1状态修改---controller
@PutMapping("status/{id}/{status}")
public SysResult updateStatus( User user){
userService.updateStatus(user);
return SysResult.success();
}
3.2状态修改---service
@Override
@Transactional
public void updateStatus(User user) {
userMapper.updateById(user);
}
4.用户新增
4.1用户新增---controller
@PostMapping("addUser")
public SysResult addUser(@RequestBody User user){
userService.addUser(user);
return SysResult.success();
}
4.2用户新增---service
@Override
@Transactional
public void addUser(User user) {
String password = user.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());
user.setPassword(password)
.setStatus(true);
.setCreated(new Date())
.setUpdated(user.getCreated());
userMapper.insert(user);
}
思路:获取密码加密后对user对象进行设置,添加创建时间,添加user对象
5.用户信息更改
5.1用户信息更改---controller
@GetMapping("/{id}")
public SysResult getuser(@PathVariable Integer id){
User user=userService.getuser(id);
return SysResult.success(user);
}
@PutMapping("updateUser")
public SysResult updateUser(@RequestBody User user){
userService.updateuser(user);
return SysResult.success();
}
思路:进行查询返回user对象,前端拿到user对象进行输入框的双向绑定,用户点击修改,把数据发送给后端进行修改
5.2用户信息更改---service
@Override
@Transactional
public void updateuser(User user) {
userMapper.updateById(user);
}
@Override
@Transactional
public User getuser(Integer id) {
return userMapper.selectOne(new QueryWrapper<User>().eq("id", id));
}
6.用户信息删除
6.1用户信息删除---controller
@DeleteMapping("/{id}")
public SysResult deleteuser( @PathVariable Integer id){
userService.deleteuser(id);
return SysResult.success();
}
6.2用户信息删除---service
@Override
@Transactional
public void deleteuser(Integer id) {
userMapper.deleteById(id);
}
思考:写了这么多了,虽然我们写的都是正确的,但是程序总会遇到千奇百怪的错误,那么我们遇到错误又改如何呢?
答:AOP面向切面编程解决
@RestControllerAdvice
public class SystemAOP {
/*当前的controller层只拦截运行时异常*/
@ExceptionHandler(RuntimeException.class)
public SysResult exception(Exception e){
/*控制台打印异常,必须加*/
e.printStackTrace();
return SysResult.fail2();
}
}
这里只拦截Controller层的异常,这是因为异常会向上抛出,所以其他层的异常都会抛出到controller层,所以只拦截Controller层的异常就可以了,注意异常类型的改变,有异常我们不能暴漏给用户,所以我们返回一个错误信息,前端页面得到信息后进行错误信息选择然后弹出提示信息。
知识点:在完成数据库入库操作时,其中创建时间和修改时间 都是用户必须添加的参数.但是该参数完全由用户填写,显得不智能. 公共的内容应该由框架完成.
添加自动填充实现类:
交给spring来管理
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/*完成入库操作时填充数据 created/updated*/
@Override
public void insertFill(MetaObject metaObject) {
Date date=new Date();
this.setFieldValByName("created",date,metaObject);
this.setFieldValByName("updated", date,metaObject);
}
/*完成修改操作时填充数据 updated*/
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updated", new Date(),metaObject);
}
}
四、商品分类模块
1.数据库的设计
字段:id、parent_id、name、status、level、created、updated
数据库原始数据:

2.实体类的创建
@TableName("item_cat")
@Data
@Accessors(chain = true)
public class ItemCat extends BasePojo{
@TableId(type = IdType.AUTO)
private Integer id; //定义主键
private Integer parentId; //定义父级菜单
private String name; //分类名称
private Boolean status; //分类状态 0 停用 1 正常
private Integer level; //商品分类等级 1 2 3
@TableField(exist = false)
private List<ItemCat> children;
}
children:用来封装子集数据
3.分类页面展现
3.1分类页面展现---controller
@GetMapping("findItemCatList/{level}")
public SysResult getItemCatList(@PathVariable Integer level){
List<ItemCat> itemCatList= itemCatService.findItemCatList(level);
return SysResult.success(itemCatList);
}
思路:通过等级查询返回分类信息
3.2分类页面展现---service
方式一:
@Override
public List<ItemCat> findItemCatList(Integer level) {
long creattime=System.currentTimeMillis();
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", 0);
List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
for (ItemCat one :
oneList) {
queryWrapper.clear();
queryWrapper.eq("parent_id", one.getId());
List<ItemCat> twoList=itemCatMapper.selectList(queryWrapper);
one.setChildren(twoList);
for (ItemCat two :
twoList ) {
queryWrapper.clear();
queryWrapper.eq("parent_id", two.getId());
List<ItemCat> threeList=itemCatMapper.selectList(queryWrapper);
two.setChildren(threeList);
}
}
long endtime=System.currentTimeMillis();
System.out.println(endtime-creattime);
return oneList;
}
思路:查询父级id为0的集合(一级分类),对集合进行遍历,根据把id作为父级id的条件查询出二级分类封装到children里,再对二级分类遍历,把二级分类的id作为父级id进行查询出三级分类,然后封装到二级分类的children里。这样虽然可行,但是嵌套循环导致大量的查询数据库导致程序变得非常的慢,我们因该减少查询数据库的次数
方式二:
程序设计:
1.数据结构 Map<父级ID,子级列表> 列表信息中不包含嵌套关系
例如: Map<0,一级列表信息> 一级列表不包含二级/三级
Map<一级ID,二级列表信息> 只有2级列表 不包含3级
Map<二级ID,三级列表信息> 只有3级列表信息.
2.根据数据结构动态根据level查询子级.
第一步:首先获取所有集合(查询一次数据库)进行分类
private Map<Integer, List<ItemCat>> getMap() {
Map<Integer,List<ItemCat>> map = new HashMap<>();
List<ItemCat> list = itemCatMapper.selectList(null);
for(ItemCat itemCat : list){
//获取parentId
int parentId = itemCat.getParentId();
if(map.containsKey(parentId)){
//key存在
map.get(parentId).add(itemCat);
}else{
//key不存在
List<ItemCat> childrenList = new ArrayList<>();
childrenList.add(itemCat);
//将第一个元素封装到map中
map.put(parentId,childrenList);
}
}
return map;
}
思路:一个方法里封装一个map集合,k是父级id,v是属于父级的儿子的集合,然后查询所有集合,条件为null,进行遍历,获取它们的父级id当作k,如果k存在,则添加到属于这个k的集合中去,如果不存在就创建一个属于这个k的集合把自己添加进去,再添加一个map集合把list集合封装进去,最后返回这个map集合
第二步:封装二级分类
private List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
List<ItemCat> oneList = map.get(0);
for(ItemCat oneItemCat :oneList){
int parentId = oneItemCat.getId();
List<ItemCat> twoList = map.get(parentId);
oneItemCat.setChildren(twoList);
}
return oneList;
}
思路:一个方法里参数为map集合,先获取一级分类,进行遍历,获取它们的id,在map集合里根据它们的id得出二级分类的集合,再把它们封装到children里
第三步:获取第三级分类
private List<ItemCat> getThreeList(Map<Integer, List<ItemCat>> map) {
List<ItemCat> oneList = getTwoList(map);
for(ItemCat oneItemCat : oneList){
List<ItemCat> twoList = oneItemCat.getChildren();
if(twoList == null || twoList.size() == 0){
System.out.println("执行跳过循环操作");
continue;
}
for (ItemCat twoItemCat : twoList){
int parentId = twoItemCat.getId();
List<ItemCat> threeList = map.get(parentId);
twoItemCat.setChildren(threeList);
}
}
return oneList;
}
思路:调用获取一级分类的方法,且这时一级分类封装了children,进行遍历,获取集合里每一个的children属性为二级分类集合,进行判断,如果集合为空则跳过本次循环,否则遍历这个二级分类集合,获取它们的id,在map里根据id获取三级分类的集合再封装到二级分类的children里
第四步:总和
@Override
public List<ItemCat> findItemCatList(Integer level) {
long startTime = System.currentTimeMillis();
Map<Integer,List<ItemCat>> map = getMap();
if(level == 1){
return map.get(0);
}
if(level == 2){
return getTwoList(map);
}
List<ItemCat> list = getThreeList(map);
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime - startTime)+"毫秒");
return list;
}
思路:如果参数为1,则直接返回父级id为0的就是一级分类
如果参数为2,则调用gettwoList方法,传入map集合
如果参数为3,则调用getThreeList方法,传入map集合
这里可以看到运行速度大大的提高了,以后还会学到redis缓存技术,以后再来和大家分享。
API:map.containsKey(key) map里判断key是否存在
4.商品分类状态修改
4.1商品分类状态修改---controller
@PutMapping("status/{id}/{status}")
public SysResult putstatus(ItemCat itemCat){
itemCatService.putstatus(itemCat);
return SysResult.success();
}
4.2商品分类状态修改---service
@Override
public void putstatus(ItemCat itemCat) {
itemCatMapper.updateById(itemCat);
}
5.商品分类新增
5.1商品分类新增---controller
@PostMapping("saveItemCat")
public SysResult addItemCat(@RequestBody ItemCat itemCat){
itemCatService.addItemCat(itemCat);
return SysResult.success();
}
5.2商品分类新增---service
@Override
@Transactional
public void addItemCat(ItemCat itemCat) {
itemCat.setStatus(true);
itemCatMapper.insert(itemCat);
}
思路:新增商品状态设置为可用
6.商品分类修改
6.1商品分类修改---controller
@PutMapping("updateItemCat")
public SysResult updateItemCat(@RequestBody ItemCat itemCat){
itemCatService.updateItemCat(itemCat);
return SysResult.success();
}
6.2商品分类修改---service
@Override
public void updateItemCat(ItemCat itemCat) {
itemCatMapper.updateById(itemCat);
}
7.商品分类删除
7.1商品分类删除---controller
@DeleteMapping("deleteItemCat")
public SysResult deleteItemCat(ItemCat itemCat){
itemCatService.deleteItemCat(itemCat);
return SysResult.success();
}
7.2商品分类删除---service
@Override
public void deleteItemCat(ItemCat itemCat) {
int level=itemCat.getLevel();
if(level==3){
itemCatMapper.deleteById(itemCat.getId());
}
if(level==2){
itemCatMapper.delete(new QueryWrapper<ItemCat>().eq("parent_id", itemCat.getId()).or().eq("id", itemCat.getId()));
}
if(level==1){
QueryWrapper<ItemCat> queryWrapper= new QueryWrapper<ItemCat>()
.eq("parent_id", itemCat.getId());
//获取二级id集合
List twoids = itemCatMapper.selectObjs(queryWrapper);
//删除三级的数据
queryWrapper.clear();
queryWrapper.in(twoids.size()>0,"parent_id", twoids)
.or()
.eq("parent_id",itemCat.getId())
.or()
.eq("id", itemCat.getId());
itemCatMapper.delete(queryWrapper);
}
}
思路:如果要删除的不是最后一级,那么要把它的子级也删除掉,我们根据等级来做删除操作,如果判断要删除的是第三级,那么直接获取id删除即可;如果要删除的是二级分类,那么应该删除它的三级子级,根据二级的id作为三级的父类id进行删除三级,再删除二级;如果删除的是一级分类,那么应该把自己的id作为父类id获取到二级分类的id集合,判断二级集合是否为空,不为空的话设置为条件构造器的in条件,或者等于二级id,或者等于自己本身。然后调用删除方法删除这个条件构造器。
五、商品列表模块
1.数据库的设计
字段:id、title、sell_point、price、num、images、item_cat_id、status、created、updated
数据库原始数据:
2.实体类的创建
@TableName("item")
@Data
@Accessors(chain = true)
public class Item extends BasePojo{
@TableId(type = IdType.AUTO)
private Integer id; //商品Id号
private String title; //商品标题信息
private String sellPoint; //卖点信息
private Integer price; //商品价格
private Integer num; //商品数量
private String images; //商品图片
private Integer itemCatId; //商品分类ID号
private Boolean status; //状态信息 0 下架 1 上架
}
3.页面展现
3.1页面展现---controller
@GetMapping("getItemList")
public SysResult getItemList(PageResult pageResult){
pageResult=itemService.getItemList(pageResult);
return SysResult.success(pageResult);
}
3.2页面展现---service
@Override
@Transactional
public PageResult getItemList(PageResult pageResult) {
Page<Item> page = new Page<>(pageResult.getPageNum(), pageResult.getPageSize());
boolean x= StringUtils.hasLength(pageResult.getQuery());
page=itemMapper.selectPage(page,new QueryWrapper<Item>().like(x, "title",pageResult.getQuery()));
pageResult.setTotal(page.getTotal()).setRows(page.getRecords());
return pageResult;
}
思路:此分页实现逻辑同商品分类分页
4.删除商品
4.1删除商品---controller
@DeleteMapping("deleteItemById")
public SysResult deleteItemById(Item item){
itemService.deleteItemById(item);
return SysResult.success();
}
4.2删除商品---service
@Override
public void deleteItemById(Item item) {
itemDescMapper.deleteById(item);
itemMapper.deleteById(item);
}
5.商品状态修改
5.1商品状态修改---controller
@PutMapping("updateItemStatus")
public SysResult updateItemStatus(@RequestBody Item item){
itemService.updateItemStatus(item);
return SysResult.success();
}
5.2 商品状态修改---service
@Override
@Transactional
public void updateItemStatus(Item item) {
itemMapper.updateById(item);
}
6.修改商品属性
6.1修改商品属性---controller
@PutMapping("updateItem")
public SysResult updateItem(@RequestBody Item item){
itemService.updateItem(item);
return SysResult.success();
}
6.2修改商品属性---service
@Override
public void updateItem(Item item) {
itemMapper.updateById(item);
}
7.添加商品
7.1.添加商品VO类的创建
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ItemVO { //该对象封装商品所有的参数信息
private Item item;
private ItemDesc itemDesc;
private ItemParam itemParam;
}
7.2添加商品ItemDesc类的创建
@TableName("item_desc")
@Data
@Accessors(chain = true)
public class ItemDesc extends BasePojo{
//item和itemDesc一对一id相同
//简化了外键约束
@TableId
private Integer id;
private String itemDesc;
}
7.3ItemParam类的创建
@TableName("item_param")
@Data
@Accessors(chain = true)
public class ItemParam extends BasePojo{
private Integer id;
private String dynamicArgs;
private String staticArgs;
//为了实现参数封装添加的数据
@TableField(exist = false)
private ItemParamVO[] dynamicArray;
@TableField(exist = false)
private ItemParamVO[] staticArray;
}
7.4添加商品页面展现---controller
@PostMapping("saveItem")
public SysResult saveItem(@RequestBody ItemVO itemVO){
itemService.saveItem(itemVO);
return SysResult.success();
}
7.5添加商品页面展现---servicec
@Override
@Transactional
public void saveItem(ItemVO itemVO) {
//item主键自增,数据入库之后才会有主键
Item item = itemVO.getItem();
item.setStatus(true);
itemMapper.insert(item);
//入库之后才有主键,现在为null
//MP自动将主键的回显功能实现
//itemId和ItemDescId是一样的
ItemDesc itemDesc = itemVO.getItemDesc();
itemDesc.setId(item.getId());
itemDescMapper.insert(itemDesc);
}
思路:详情信息和商品页面展现信息是分开的,解耦,缓和数据库压力,提高页面展现速度,所以商品的id==商品详情的id,商品入库之后回显id,再把id赋值给商品详情,商品详情再次入库。(商品详情存的都是大字段,存储html的代码片段)
8.文件上传
8.1文件上传VO类创建
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ImageVO implements Serializable {
private String virtualPath;//虚拟地址
private String urlPath;//url地址
private String fileName;//文件名称
}
8.2文件上传---controller
@RestController
@RequestMapping("file")
@CrossOrigin
public class FileController {
@Autowired
private FileService fileService;
@PostMapping("upload")
public SysResult upload(MultipartFile file) throws IOException {
ImageVO imageVO=fileService.upload(file);
if(imageVO==null){//业务执行有误
return SysResult.fail();
}
return SysResult.success(imageVO);
}
思路:把文件传入,如果业务层返回null则出错,否则直接返回imageVO对象
8.3文件上传---service
@Service
@PropertySource("classpath:/image.properties")//放在配置文件里进行配置 和@value合用
public class FileServiceimpl implements FileService{
@Value("${image.localDir}")
private String localDir; //="D:/images";
@Value("${image.preUrl}")
private String preUrl; //="http://image.jt.com";
@Override
public ImageVO upload(MultipartFile file) {
//完成校验:
//1.获取图片名称
String filename = file.getOriginalFilename();
//bug:windows不区分大小写,所以我们全部变小写
filename = filename.toLowerCase();
//正则
if(!filename.matches("^.+\\.(jpg|png|gif)$")){
//不是图片返回null
return null;
}
//2.判断是否满足图片固有属性
try {
BufferedImage bufferedImage=ImageIO.read(file.getInputStream());
int width=bufferedImage.getWidth();
int height=bufferedImage.getHeight();
if(width==0||height==0){
return null;
}
//3.为了提高查询效率,分目录存储 时间--yyyy/MM/dd
String dateDir = new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());
String localDirPath=localDir+dateDir;
//创建目录
File dirFile = new File(localDirPath);
if(!dirFile.exists()){
dirFile.mkdirs();
}
//4.使用UUID替换文件名称
//唯一:系统内部唯一
String uuid= UUID.randomUUID().toString().replace("-", "");
//截取文件的后缀名
int index=filename.lastIndexOf(".");
String fileType=filename.substring(index);
String newFileName=uuid+fileType;
//5.文件上传 目录/文件名称
String realFilePath=localDirPath+newFileName;
file.transferTo(new File(realFilePath));
System.out.println("文件上传成功");
//6.封装返回值
String virtualPath=dateDir+newFileName;
//自定义url
String url=preUrl+virtualPath;
System.out.println("磁盘地址"+realFilePath);
System.out.println("网络地址"+url);
return new ImageVO(virtualPath,url,newFileName);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
思路:首先获取图片名称,为了后面的验证,我们先全部变成小写,进行正则验证是否符合图片格式,再次检查是不是符合图片格式的有害软件伪装的通过获取图片固有属性判断图片的长和宽,定义存放图片文件的地址用来存放图片,提取地址头与用时间分隔命名的文件夹拼接形成每天存放图片子文件目录,先判断是否存在,不存在就创建,为了防图片名称相同,我们用UUID来重命名图片名称,然后进行文件上传,进行封装虚拟路径封装自定义的url进行返回,这里用到了nginx反向代理技术技术,我们后面再说,如果上述有步骤出错,那么catch返回null,前端输出执行出错。
知识点:
API:
- MultipartFile输入流类,可以自动关流
- file.getOriginalFilename()获取图片名称
- toLowerCase()全部变小写
- filename.matches("^.+\\.(jpg|png|gif)$")正则判断
- BufferedImage bufferedImage=ImageIO.read(file.getInputStream());图片固有属性
- String dateDir = new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());定义时间规则
- lastIndexOf从后向前数,第一个参数是到什么位置
- file.transferTo(new File(realFilePath));文件上传,参数是目录加文件名称
动态定义全局变量:
image.properties配置文件里 K=V格式
image.localDir=/usr/local/src/images image.preUrl=http://image.jt.com
Service类里:
在类头上加:@PropertySource("classpath:/image.properties")参数后面是配置文件名
在成员变量上加:@Value("${image.localDir}") 这是配置文件里K的写法,把V赋值给下面的成员变量
常用正则表达式:
详细请看:正表达式语法大全




Nginx反向代理服务:项目结束和发布一起总结
9.图片删除
9.1图片删除---controller
@DeleteMapping("deleteFile")
public SysResult deleteFile(String virtualPath){
fileService.deleteFile(virtualPath);
return SysResult.success();
}
9.2图片删除---service
@Override
public void deleteFile(String virtualPath) {
String delepath=localDir+virtualPath;
File file = new File(delepath);
if(file.exists()){
file.delete();
}
}
到这里这个不太完整的项目就结束了,大部分 都是一些基本的CRUD,知识根据具体的业务不同做一些逻辑的操作,这也是前面所学的一些综合,权限还没有做,因为我还在学习shiro中,希望能学的会吧,明天说一下Nginx和linux的项目部署发布!
这两天忙的要死啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
满怀希望就会所向披靡!!!
3988



被折叠的 条评论
为什么被折叠?



