SpringBootWeb案例 Part3
目录
1. 新增员工
1.1 需求
1.2 接口文档
1.3 思路分析
@PostMapping
@RequestBody //把前端传递的JSON数据填充到实体类中
1.4 功能开发
1.5 功能测试
1.6 前后端联调
2. 文件上传
2.1 文件上传简介
Spring中提供了一个API:MultipartFile,使用这个API就可以在服务端来接收到上传的文件
2.2 本地存储
UploadController
编辑利用Postman测试:
总结:MultipartFile 常见方法
2.3 阿里云OSS
2.3.1 准备
使用第三方服务 - 通用思路
阿里云OSS-使用步骤
2. OSS开通
2.3.2 入门
阿里云OSS相关依赖:
代码实现:
2.3.3 项目集成阿里云OSS
前面我们已经实现了员工信息的动态条件分页查询以及批量删除操作。 关于员工管理的功能,还有两个需要实现:
-
新增员工
-
修改员工
首先我们先完成"新增员工"的功能开发,再完成"修改员工"的功能开发。而在"新增员工"中,需要添加头像,而头像需要用到"文件上传"技术。 当整个员工管理功能全部开发完成之后,我们再通过配置文件来优化一些内容。
综上所述,我们今天的课程内容包含以下四个部分:
-
新增员工
-
文件上传
-
修改员工(编辑 => 数据回显)
-
配置文件
1. 新增员工
1.1 需求
在新增用户时,我们需要保存用户的基本信息,并且还需要上传的员工的图片,目前我们先完成第一步操作,保存用户的基本信息。
1.2 接口文档
我们参照接口文档来开发新增员工功能
-
基本信息
- 请求参数
参数格式:application/json
参数说明:
名称 | 类型 | 是否必须 | 备注 |
---|---|---|---|
username | string | 必须 | 用户名 |
name | string | 必须 | 姓名 |
gender | number | 必须 | 性别, 说明: 1 男, 2 女 |
image | string | 非必须 | 图像 |
deptId | number | 非必须 | 部门id |
entrydate | string | 非必须 | 入职日期 |
job | number | 非必须 | 职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师 |
- 请求数据样例:
响应数据
参数格式:application/json
参数说明:
参数名 | 类型 | 是否必须 | 备注 |
---|---|---|---|
code | number | 必须 | 响应码,1 代表成功,0 代表失败 |
msg | string | 非必须 | 提示信息 |
data | object | 非必须 | 返回的数据 |
- 响应数据样例:
1.3 思路分析
新增员工的具体的流程:
接口文档规定:
请求路径:/emps
请求方式:POST
请求参数:JSON格式数据
响应数据:JSON格式数据
问题1:如何限定请求方式是POST?
@PostMapping
问题2:怎么在Controller中接收JSON格式的请求参数?
@RequestBody //把前端传递的JSON数据填充到实体类中
1.4 功能开发
EmpController
package com.gch.controller;import com.gch.pojo.Emp;
import com.gch.pojo.PageBean;
import com.gch.pojo.Result;
import com.gch.service.EmpService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;import java.awt.event.WindowFocusListener;
import java.time.LocalDate;
import java.util.List;/**员工管理控制器*/
@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {@Autowiredprivate EmpService empService;/*** 条件分页查询* @param page 分页查询的页码* @param pageSize 分页查询的每页展示记录数* @param name 姓名* @param gender 性别* @param begin 入职日期的开始时间* @param end 入职日期的结束时间* 默认值的设置可以通过注解@RequestParam中的defaultValue()属性来指定默认值* 用@DateTimeFormat注解中的pattern属性指定日期时间类型的格式* 注意:方法签名上的形参变量名需要于接口文档中的请求参数名保持一致* @return*/@GetMappingpublic Result page(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "10") Integer pageSize,String name, Short gender,@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
// 设置默认值,但写法比较繁琐
// if(page == null) {page = 1;}
// if(pageSize == null) {pageSize = 10;}// 记录日志log.info("条件分页查询,参数:page:{},pageSize:{},name:{},gender:{},begin:{},end:{}",page,pageSize,name,gender,begin,end);// 调用service分页查询PageBean pageBean = empService.page(page,pageSize,name,gender,begin,end);// 响应return Result.success(pageBean);}/*** 批量删除员工信息* @param ids 接收前端传递过来的路径参数id数组* @return*/@DeleteMapping("/{ids}")public Result delete(@PathVariable List<Integer> ids) {// 记录日志log.info("批量删除员工,ids:{}",ids);// 调用service批量删除empService.delete(ids);// 响应return Result.success();}// 新增员工/*** 新增员工* @param emp 员工对象* 在Controller中使用@RequestBody注解接收前端传递的JSON格式的数据并填充到实体类中* @return 返回统一响应结果*/@PostMappingpublic Result save(@RequestBody Emp emp) {// 记录日志log.info("新增员工 , emp:{}",emp);// 调用service添加员工empService.save(emp);// 响应return Result.success();}
}
EmpService
package com.gch.service;import com.gch.pojo.Emp;
import com.gch.pojo.PageBean;import java.time.LocalDate;
import java.util.List;/**员工业务规则*/
public interface EmpService {/*** 条件分页查询* @param page => 分页查询的页码* @param pageSize => 分页查询的每页展示记录数* @param name => 姓名* @param gender => 性别* @param begin => 入职日期的开始时间* @param end => 入职日期的结束时间* @return*/PageBean page(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end);/*** 批量删除员工信息操作* @param ids 前端传递过来的路径参数id集合*/void delete(List<Integer> ids);/*** 添加员工* @param emp 员工对象*/void save(Emp emp);
}
EmpServiceImpl
package com.gch.service.impl;import com.gch.mapper.EmpMapper;
import com.gch.pojo.Emp;
import com.gch.pojo.PageBean;
import com.gch.service.EmpService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;/**员工业务实现类*/
@Slf4j
@Service
public class EmpServiceImpl implements EmpService {@Autowiredprivate EmpMapper empMapper;/*** 原始分页查询* @param page => 分页查询的页码* @param pageSize => 分页查询的每页展示记录数* @return*/
// @Override
// public PageBean page(Integer page, Integer pageSize) {
// // 1.获取总记录数
// Long total = empMapper.count();
//
// // 2.获取分页查询的数据列表
// List<Emp> rows = empMapper.pageSelect((page - 1) * pageSize,pageSize);
//
// // 3.封装PageBean对象
// return new PageBean(total,rows);
// }/*** 基于PageHelper分页插件实现分页查询* @param page => 分页查询的页码* @param pageSize => 分页查询的每页展示记录数* @param name => 姓名* @param gender => 性别* @param begin => 入职日期的开始时间* @param end => 入职日期的结束时间* @return*/@Overridepublic PageBean page(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end){// 1.设置分页参数PageHelper.startPage(page,pageSize);// 2.执行条件分页查询List<Emp> empList = empMapper.list(name,gender,begin,end);// 获取条件分页查询结果Page<Emp> p = (Page<Emp>)empList;// 3.封装PageBean对象并返回return new PageBean(p.getTotal(),p.getResult());}/*** 批量删除员工信息* @param ids 前端传递过来的路径参数id集合*/@Overridepublic void delete(List<Integer> ids) {empMapper.deleteById(ids);}/*** 添加员工* @param emp 员工对象*/@Overridepublic void save(Emp emp) {// 1.补全员工数据 / 属性emp.setCreateTime(LocalDateTime.now());emp.setUpdateTime(LocalDateTime.now());// 2.调用mapper层新增员工方法empMapper.add(emp);}
}
EmpMapper
package com.gch.mapper;import com.gch.pojo.Emp;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.time.LocalDate;
import java.util.List;/**员工管理*/
@Mapper
public interface EmpMapper {/*** 查询总记录数* @return*/
// @Select("select count(*) from tlias.emp")
// public Long count();/*** 分页查询,获取数据列表/获取列表数据* @param startIndex => 起始索引* @param pageSize => 每页展示记录数* @return*/
// @Select("select * from tlias.emp limit #{startIndex},#{pageSize}")
// public List<Emp> pageSelect(Integer startIndex,Integer pageSize);/*** 基于PageHelper进行员工信息条件分页查询* 查询条件参数:* @param name => 姓名* @param gender => 性别* @param begin => 入职日期的开始时间* @param end => 入职日期的结束时间* @return*/public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);/*** 批量删除员工信息* @param ids 前端传递过来的路径参数id集合*/void deleteById(List<Integer> ids);/*** 新增/插入员工* @param emp 员工对象*/@Insert("insert into tlias.emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +"values(#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")void add(Emp emp);
}
1.5 功能测试
代码开发完成后,重启服务器,打开Postman发送 POST 请求,请求路径:http://localhost:8080/emps
1.6 前后端联调
功能测试通过后,我们再进行通过打开浏览器,测试后端功能接口:
总结:JSON格式的参数如何接收? @RequestBody
2. 文件上传
在我们完成的新增员工功能中,还存在一个问题:没有头像(图片缺失)
上述问题,需要我们通过文件上传技术来解决。下面我们就进入到文件上传技术的学习。
文件上传技术这块我们主要学习三个方面:首先我们先对文件上传做一个整体的介绍,接着再学习文件上传的本地存储方式,最后学习云存储方式。
文件上传常见的2种存储形式:一种是本地存储方式,一种是云(服务)存储方式。
云存储会采用当前最为流行的阿里云的对象存储服务OSS。
2.1 文件上传简介
- 文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程。
- 文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。
不管是移动端(微信发朋友圈)还是PC端,文件上传技术都随处可见!
在我们的案例中,在新增员工的时候,要上传员工的头像,此时就会涉及到文件上传的功能。在进行文件上传时,我们点击加号或者是点击图片,就可以选择手机或者是电脑本地的图片文件了。当我们选择了某一个图片文件之后,这个文件就会上传到服务器,从而完成文件上传的操作。
想要完成文件上传这个功能需要涉及到两个部分:
-
前端程序
-
服务端程序
前端我点击一个按钮或者是点击添加图片,是如何打开 / 调用本地系统窗口的呢?
- 我们先来看看在前端程序中要完成哪些代码:
<form action="/upload" method="post" enctype="multipart/form-data">姓名: <input type="text" name="username"><br>年龄: <input type="text" name="age"><br>头像: <input type="file" name="image"><br><input type="submit" value="提交">
</form>
文件上传 - 前端页面的三要素
- 前端要想进行文件上传,必须在前端页面定义这样一个form表单,并且在表单当中,要定义一个表单项,它的类型type为file,一旦设置type为file之后,这个表单项最终在页面的体现形式就是这样的一个按钮叫选择文件或者叫浏览,点击该按钮之后,就会弹出一个窗口,就可以来选择我们要上传的本地文件了,这就是第一要素,必须要有一个表单项的类型type为file;
- 第二个要素:表单的提交方式必须是post方式,因为我们要进行文件上传,而这些文件一般都会比较大,所以我们要使用post提交方式,在请求体当中将我们的文件内容提交到服务端;
- 第三个要素:在form表单当中,我们需要通过enctype属性来指定表单的编码格式为multipart/form-data,因为普通默认的编码格式是不适合传输大型的二进制数据的,如果表单的编码格式选择的是默认值,此时提交的仅仅是所上传文件的文件名,文件里面的内容是不会提交到服务端的。 boundary:分隔符 如果指定表单的编码格式为multipart/form-data,表单会分为多个部分提交,每一个部分之间都有一个分隔符。
上述这三项我们就称之为文件上传 - 前端页面的三要素。
上传文件的原始form表单,要求表单必须具备以下三点(上传文件页面三要素):
-
表单必须有file域,用于选择要上传的文件
<input type="file" name="image"/>
-
表单提交方式必须为POST
通常上传的文件会比较大,所以需要使用 POST 提交方式
-
表单的编码类型enctype属性必须要设置为:multipart/form-data
普通默认的编码格式是不适合传输大型的二进制数据的,所以在文件上传时,表单的编码格式必须设置为multipart/form-data
前端页面我们需要放在SpringBoot项目的static目录下。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>上传文件</title>
</head>
<body><form action="/upload" method="post" enctype="multipart/form-data">姓名: <input type="text" name="username"><br>年龄: <input type="text" name="age"><br>头像: <input type="file" name="image"><br><input type="submit" value="提交"></form></body>
</html>
知道了前端程序中需要设置上传文件页面三要素,那我们的后端程序又是如何实现的呢?
-
首先在服务端定义这么一个controller,用来进行文件上传,然后在controller当中定义一个方法来处理
/upload
请求 -
在定义的方法中接收提交过来的数据 (方法中的形参名和请求参数的名字保持一致)
-
用户名:String name
-
年龄: Integer age
-
文件: MultipartFile image
Spring中提供了一个API:MultipartFile,使用这个API就可以在服务端来接收到上传的文件
-
问题:如果表单项的名字和方法中形参名不一致,该怎么办?
解决:使用@RequestParam注解进行参数绑定
public Result upload(String username,Integer age, @RequestParam("image") MultipartFile file) //file形参名和请求参数名image不一致
UploadController代码:
package com.gch.controller;import com.gch.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;@Slf4j
@RestController
@RequestMapping("/upload")
public class UploadController {/*** 文件上传* @param username 姓名* @param age 年龄* @param image 头像* @return*/@PostMappingpublic Result upload(String username, Integer age, MultipartFile image) {// 记录日志log.info("文件上传:姓名:{}, 年龄:{}, 头像:{}", username, age, image);// 响应return Result.success();}
}
后端程序编写完成之后,打个断点,以debug方式启动SpringBoot项目,基于8080端口的Tomcat来访问upload.html,这样,表单在提交时,才可以访问upload.html这个路径。
打开浏览器输入:http://localhost:8080/upload.html , 录入数据并提交
通过后端程序控制台可以看到,上传的文件是存放在一个临时目录
打开临时目录可以看到以下内容:
表单提交的三项数据(姓名、年龄、文件),分别存储在不同的临时文件中:
当我们程序运行完毕之后,也就是请求响应完毕之后这个临时文件会自动删除。
所以,我们如果想要实现文件上传,需要将这个临时文件,要转存到我们的磁盘目录中。
2.2 本地存储
前面我们已分析了文件上传功能前端和后端的基础代码实现,文件上传时在服务端会产生一个临时文件,请求响应完成之后,这个临时文件被自动删除,并没有进行保存。下面呢,我们就需要完成将上传的文件保存在服务器的本地磁盘上。
本地存储:在服务端,接收到上传上来的文件之后,将文件存储在本地服务器磁盘目录当中。
代码实现:
-
在服务器本地磁盘上创建images目录,用来存储上传的文件(例:C盘创建images目录)
-
使用MultipartFile类提供的API方法,把临时文件转存到本地磁盘目录下
MultipartFile 常见方法:
String getOriginalFilename(); //获取原始文件名
void transferTo(File dest); //将接收的文件转存到磁盘文件中
long getSize(); //获取文件的大小,单位:字节
byte[] getBytes(); //获取文件内容的字节数组
InputStream getInputStream(); //获取接收到的文件内容的输入流
UploadController
package com.gch.controller;import com.gch.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;@Slf4j
@RestController
@RequestMapping("/upload")
public class UploadController {/*** 本地存储实现文件上传* @param username 姓名* @param age 年龄* @param image 头像* @return*/@PostMappingpublic Result upload(String username, Integer age, MultipartFile image) throws IOException {// 1.记录日志log.info("文件上传:姓名:{}, 年龄:{}, 头像:{}", username, age, image);// 2.获取原始文件名String originalName = image.getOriginalFilename();// 3.将接收到的文件存储在服务器的磁盘目录当中 C:\imagesimage.transferTo(new File("C:\\images\\" + originalName));// 4.响应return Result.success();}
}
利用Postman测试:
注意:请求参数名和Controller方法形参名保持一致
通过postman测试,我们发现文件上传是没有问题的。但是由于我们是使用原始文件名作为所上传文件的存储名字,当我们再次上传一个名为1.jpg文件时,发现会把之前已经上传成功的文件覆盖掉。
解决方案:保证每次上传文件时文件名都唯一的(使用UUID获取随机文件名)
package com.gch.controller;import com.gch.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.util.UUID;@Slf4j
@RestController
@RequestMapping("/upload")
public class UploadController {/*** 文件上传* @param username 姓名* @param age 年龄* @param image 头像* @return*/@PostMappingpublic Result upload(String username, Integer age, MultipartFile image) throws IOException {// 1.记录日志log.info("文件上传:姓名:{}, 年龄:{}, 头像:{}", username, age, image);// 2.获取原始文件名 - 1.jpgString originalName = image.getOriginalFilename();// 构造唯一的文件名(不能重复) - UUID(通用唯一识别码)-36位长度固定的字符串// 注意:不能直接使用UUID作为文件名,因为文件名都是带有后缀的,比如.txt,.png,而UUID没有后缀// 所以,唯一的文件名思路:随机的UUID + 文件扩展名// 3.文件扩展名String extName = originalName.substring(originalName.lastIndexOf("."));// 4.构建新的文件名:随机的UUID + 文件扩展名String newFileName = UUID.randomUUID().toString() + extName;// 记录日志log.info("新的文件名:{}",newFileName);// 5.将接收到的文件存储在服务器的磁盘目录当中 C:\imagesimage.transferTo(new File("C:/images/" + newFileName));// 6.响应return Result.success();}
}
再次打开Postman进行测试:
在解决了文件名唯一性的问题后,我们再次上传一个较大的文件(超出1M)时发现,后端程序报错: FileSizeLimitExceededException
报错原因呢是因为:在SpringBoot中,文件上传时默认单个文件最大大小为1M
那么如果需要上传大文件,可以在application.properties进行如下配置:
#配置单个文件最大上传大小的限制,默认是1MB
spring.servlet.multipart.max-file-size=10MB
#配置单个请求最大上传大小的限制(一次请求可以上传多个文件),默认是10MB
spring.servlet.multipart.max-request-size=100MB
到时此,我们文件上传的本地存储方式已完成了。但是这种本地存储方式还存在一些问题:
如果直接存储在服务器的磁盘目录中,存在以下缺点:
-
不安全:磁盘如果损坏,所有的文件就会丢失
-
容量有限:如果存储大量的图片,磁盘空间有限(磁盘不可能无限制扩容)
-
浏览器无法直接访问
为了解决上述问题呢,通常有两种解决方案:
-
自己搭建存储服务器,如:fastDFS这种分布式文件系统 、MinIO这种对象存储服务来搭建集群
-
使用现成的云服务,如:阿里云,腾讯云,华为云
总结:MultipartFile 常见方法
下一小节来学习通过当前最为主流的阿里云提供的对象存储服务来存储上传的文件。
2.3 阿里云OSS
2.3.1 准备
阿里云是阿里巴巴集团旗下全球领先的云计算公司,也是国内最大的云服务提供商 。
云指的就是云端,也就是互联网。
云服务指的就是通过互联网对外提供的各种各样的服务,比如像:语音服务、短信服务、邮件服务、视频直播服务、文字识别服务、对象存储服务等等。
当我们在项目开发时需要用到某个或某些服务,就不需要自己来开发了,可以直接使用阿里云提供好的这些现成服务就可以了。比如:在项目开发当中,我们要实现一个短信发送的功能,如果我们项目组自己实现,将会非常繁琐,因为你需要和各个运营商进行对接。而此时阿里云完成了和三大运营商对接,并对外提供了一个短信服务。我们项目组只需要调用阿里云提供的短信服务,就可以很方便的来发送短信了。这样就降低了我们项目的开发难度,同时也提高了项目的开发效率。(大白话:别人帮我们实现好了功能,我们只要调用即可)
云服务提供商给我们提供的软件服务通常是需要收取一部分费用的。
阿里云对象存储服务OSS(Object Storage Service),这里的对象指的就是文件,是一款海量、安全、低成本、高可靠的云存储服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。
在我们使用了阿里云OSS对象存储服务之后,我们的项目当中如果涉及到文件上传这样的业务,在前端进行文件上传并请求到服务端时,在服务器本地磁盘当中就不需要再来存储文件了。我们直接将接收到的文件上传到oss,由 oss帮我们存储和管理,同时阿里云的oss存储服务还保障了我们所存储内容的安全可靠。
那像阿里云这样的云服务提供商已经提供了这样的云服务了,那我们学习这类的云服务,主要是学习什么呢?
其实我们主要学习的是如何在项目当中来使用云服务完成具体的业务功能。而无论使用什么样的云服务,阿里云也好,腾讯云、华为云也罢,在使用第三方的服务时,操作的思路都是一样的。
使用第三方服务 - 通用思路
- 准备工作:比如我们要完成一些账号的注册、实名认证,并且登录到对应的后台来做一些基础的配置。
- 参照官方提供的SDK示例代码来编写入门程序,通过入门程序先把基本的功能和流程先测通。
- 在项目当中来集成该服务,来完成特定的业务功能。
- SDK:Software Development Kit 的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做SDK。
- 简单说,sdk中包含了我们使用第三方云服务时所需要的依赖,以及一些示例代码。我们可以参照sdk所提供的示例代码就可以完成入门程序。
第三方服务使用的通用思路,我们做一个简单介绍之后,接下来我们就来介绍一下我们当前要使用的阿里云oss对象存储服务具体的使用步骤。
阿里云OSS-使用步骤
Bucket:Bucket是阿里云OSS当中的存储空间,存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象(文件)都必须隶属于某个存储空间(Bucket),每一个文件都必须要归属于某一个存储空间(Bucket)。
- 所以,我们要想往阿里云OSS当中来存储我们上传的这些文件, 那我们就必须要去创建一个Bucket。
- 获取AccessKey:获取我们个人的一个身份凭证,叫做AccessKey,证明自己是阿里云OSS这个云服务的合法用户,里面包含两项,一项是AccessKey ID,另外一项是AccessKey Secret,也称为密钥。
- 将来我们在编写入门程序,我们在案例当中来集成OSS这个云服务的时候,都需要用到这个密钥。
2. OSS开通
(1)打开阿里云-计算,为了无法计算的价值阿里云——阿里巴巴集团旗下公司,是全球领先的云计算及人工智能科技公司之一。提供免费试用、云服务器、云数据库、云安全、云企业应用等云计算服务,以及大数据、人工智能服务、精准定制基于场景的行业解决方案。免费备案,7x24小时售后支持,助企业无忧上云。https://www.aliyun.com/ ,申请阿里云账号并完成实名认证。
下面我们根据之前介绍的使用步骤,完成准备工作:
-
注册阿里云账户(注册完成后需要实名认证)
-
注册完账号之后,就可以登录阿里云
(2)开通OSS
开通服务后,在OSS产品详情页面单击管理控制台直接进入OSS管理控制台界面。您也可以单击位于官网首页右上方菜单栏的控制台,进入阿里云管理控制台首页,然后单击左侧的对象存储OSS菜单进入OSS管理控制台界面https://oss.console.aliyun.com/overview。
(3)创建Bucket存储空间
新建Bucket,命名为 xxx ,读写权限为 ==公共读==
(4)获取AccessKey密钥
2.3.2 入门
阿里云oss 对象存储服务的准备工作我们已经完成了,接下来我们就来完成第二步操作:参照官方所提供的sdk示例来编写入门程序。
首先我们需要来打开阿里云OSS的官方文档,在官方文档中找到 SDK 的示例代码:
如果是在实际开发当中,我们是需要从前往后仔细的去阅读这一份文档的,但是由于现在是练习,我们就只挑重点的去看。
阿里云OSS相关依赖:
<!-- 阿里云OSS Java SDK--><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.15.1</version></dependency><!-- jaxb相关依赖--><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version></dependency><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version></dependency><!-- no more than 2.3.3--><dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.3</version></dependency>
在以上代码中,需要替换的内容为:
- - accessKeyId:阿里云账号AccessKey
- - accessKeySecret:阿里云账号AccessKey对应的秘钥
- - bucketName:Bucket名称
- - objectName:对象名称,在Bucket中存储的对象的名称
- - filePath:文件路径
代码实现:
package com.gch;import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import java.io.File;public class Demo {public static void main(String[] args) throws Exception {// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。{指定连接阿里云的OSS服务的地址}String endpoint = "https://oss-cn-beijing.aliyuncs.com";String accessKeyId = "YourAccessKeyId";String accessKeySecret = "YourAccessKeySecret";// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();// 填写Bucket名称,例如examplebucket。String bucketName = "gch-web-tlias01";// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。String objectName = "1.jpg";// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。String filePath= "C:\\Users\\A.G.H\\Pictures\\8-bit City_1920x1080.jpg";// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint,accessKeyId,accessKeySecret);try {// 创建PutObjectRequest对象。PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath));// 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。// ObjectMetadata metadata = new ObjectMetadata();// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());// metadata.setObjectAcl(CannedAccessControlList.Private);// putObjectRequest.setMetadata(metadata);// 上传文件。PutObjectResult result = ossClient.putObject(putObjectRequest); } catch (OSSException oe) {System.out.println("Caught an OSSException, which means your request made it to OSS, "+ "but was rejected with an error response for some reason.");System.out.println("Error Message:" + oe.getErrorMessage());System.out.println("Error Code:" + oe.getErrorCode());System.out.println("Request ID:" + oe.getRequestId());System.out.println("Host ID:" + oe.getHostId());} catch (ClientException ce) {System.out.println("Caught an ClientException, which means the client encountered "+ "a serious internal problem while trying to communicate with OSS, "+ "such as not being able to access the network.");System.out.println("Error Message:" + ce.getMessage());} finally {if (ossClient != null) {ossClient.shutdown();}}}
}
运行代码后:
运行以上程序后,会把本地的文件上传到阿里云OSS服务器上(验证):
可以看到,只要把文件交给阿里云OSS来存储和管理,它就会为每一个文件都分配这样一个URL地址,并且只要在浏览器地址栏输入URL地址,就会自动的下载我们所上传的文件。
![](https://img-blog.csdnimg.cn/fd8e0686c4fe48829fcd33ae24209362.png)
2.3.3 项目集成阿里云OSS
阿里云oss对象存储服务的准备工作以及入门程序我们都已经完成了,接下来我们就需要在案例当中集成阿里云oss对象存储服务,来存储和管理案例中上传的图片。
在新增员工的时候,上传员工的图像,而之所以需要上传员工的图像,是因为将来我们需要在系统页面当中访问并展示员工的图像。而要想完成这个操作,需要做两件事:
需要上传员工的图像,并把图像保存起来(存储到阿里云OSS)
访问员工图像(通过图像在阿里云OSS的存储地址访问图像)
OSS中的每一个文件都会分配一个访问的url,通过这个url就可以访问到存储在阿里云上的图片。所以需要把url返回给前端,此时前端就会自动的给阿里云OSS对象存储服务来发送请求,最终获取到这张图片,在页面当中将这张图片展示出来,这样前端就可以通过url获取到图像。
提问:前端获取到url之后,真的能够在页面当中将这个图片展示出来吗?我们刚才在测试的时候,我们拿到这个url之后直接在浏览器当中访问,好像是将这个图片直接下载下来了。
回答:这个大家不用担心,因为最终我们要在页面当中好在那时展示这个图片,是要通过HTML的标签来渲染展示的,也就是<img>标签。
演示:
我们参照接口文档来开发文件上传功能:
-
基本信息
- 请求参数
参数格式:multipart/form-data(文件上传的form表单)
参数说明:
参数名称 | 参数类型 | 是否必须 | 示例 | 备注 |
---|---|---|---|---|
image | file | 是 |
- 响应数据
参数格式:application/json
参数说明:
参数名 | 类型 | 是否必须 | 备注 |
---|---|---|---|
code | number | 必须 | 响应码,1 代表成功,0 代表失败 |
msg | string | 非必须 | 提示信息 |
data | object | 非必须 | 返回的数据,上传图片的访问路径 |
响应数据样例:
使用阿里云OSS对象存储服务来进行文件上传这应该是一个通用的操作!
引入阿里云OSS上传文件工具类(由官方的示例代码改造而来)
package com.gch.utils;import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.UUID;/*** 使用阿里云OSS进行文件存储的工具类*/
@Component
public class AliyunOSSUtils {/** 指定连接的阿里云OSS服务的地址 */String endpoint = "https://oss-cn-beijing.aliyuncs.com";/** 阿里云OSS AccessKey */String accessKeyId = "LTAI5tHGMz3QSEMrwu6wSmSC";String accessKeySecret = "4yBkKaSJvvqobtnK36fTGXiPreqh5P";/** 存储空间Bucket的名称 */String bucketName = "gch-web-tlias01";/*** 实现文件上传到OSS* @param multipartFile 在服务端来接收到上传的文件* @return 返回图片访问的URL* @throws IOException*/public String upload(MultipartFile multipartFile) throws IOException {/** 构造唯一的文件对象名称:UUID + 文件扩展名 */String objectName = UUID.randomUUID().toString() + multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().lastIndexOf("."));// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint,accessKeyId,accessKeySecret);try {// 创建PutObjectRequest对象。PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, multipartFile.getInputStream());// 上传文件到OSSPutObjectResult result = ossClient.putObject(putObjectRequest);} finally {if (ossClient != null) {// 关闭ossClientossClient.shutdown();}}// 返回文件访问路径return endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + objectName;}
}
根据接口文档开发文件上传接口 - 修改UploadController代码:
package com.gch.controller;import com.gch.pojo.Result;
import com.gch.utils.AliyunOSSUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.util.UUID;@Slf4j
@RestController
@RequestMapping("/upload")
public class UploadController {@Autowiredprivate AliyunOSSUtils aliyunOSSUtils;/*** 阿里云OSS对象存储服务实现文件上传* @param image 要上传的文件* @return 响应*/@PostMappingpublic Result upload(MultipartFile image) throws IOException {// 1.记录日志log.info("进行文件上传,将文件上传到阿里云OSS");// 2.调用阿里云OSS工具类,将接收到的文件上传到阿里云String url = aliyunOSSUtils.upload(image);// 3.将图片上传完成后的url返回,用于浏览器回显展示return Result.success(url);}
}
使用Postman测试:
前后端联调:
总结:
相关文章:
![](https://img-blog.csdnimg.cn/918037207908423dbfa235c2ec019420.png)
SpringBootWeb案例 Part3
目录 1. 新增员工 1.1 需求 1.2 接口文档 1.3 思路分析 PostMapping RequestBody //把前端传递的JSON数据填充到实体类中 1.4 功能开发 1.5 功能测试 1.6 前后端联调 2. 文件上传 2.1 文件上传简介 Spring中提供了一个API:MultipartFile,使…...
![](https://www.ngui.cc/images/no-images.jpg)
C++中using 用法
C中的 using 关键字用于引入命名空间、类型别名和模板别名。以下是 using 关键字的几种常见用法及其中文解析: 1. 引入命名空间: using namespace std; 中文解析:引入 std 命名空间,使得命名空间中的成员在当前作用域内可直接使…...
![](https://img-blog.csdnimg.cn/5b334bcfbab547c6a7916e943ac7c3c0.png)
window下jdk安装及更换jdk版本的一些问题。
目录 jdk安装jdk的选择。oracle的jdk怎么安装。openjdk怎么安装。 jdk的版本控制。更换jdk的一些问题。 jdk安装 jdk的选择。 目前有两种可选的jdk,oracle的和开源的Openjdk,这两种jdk的区别可以自行查阅,就结果而言,openjdk开源…...
![](https://img-blog.csdnimg.cn/img_convert/33719383053bc55fb62121d03e8ff776.png)
GPT4模型架构的泄漏与分析
迄今为止,GPT4 模型是突破性的模型,可以免费或通过其商业门户(供公开测试版使用)向公众提供。它为许多企业家激发了新的项目想法和用例,但对参数数量和模型的保密却扼杀了所有押注于第一个 1 万亿参数模型到 100 万亿参…...
GEE/PIE遥感大数据处理与典型案例丨数据整合Reduce、云端数据可视化、数据导入导出及资产管理、机器学习算法等
目录 专题一:初识GEE和PIE遥感云平台 专题二:GEE和PIE影像大数据处理基础 专题三:数据整合Reduce 专题四:云端数据可视化 专题五:数据导入导出及资产管理 专题六:机器学习算法 专题七:…...
![](https://img-blog.csdnimg.cn/257f520fff9e41c9b459b9578a882104.png)
STM32--DMA
文章目录 DMA简介DMA特性 DMA框图DMA基本结构DMA请求数据宽度对齐DMA数据转运工程DMAADC多通道 DMA简介 直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的…...
![](https://www.ngui.cc/images/no-images.jpg)
mongodb和redis的用途
MongoDB和Redis都是常见的NoSQL数据库,它们有不同的特点和用途。 MongoDB的主要特点和用途: 数据存储:MongoDB是一种面向文档的数据库,以JSON样式的BSON文档(二进制JSON)的形式存储数据。它支持复杂的数据…...
![](https://img-blog.csdnimg.cn/img_convert/26e4f78135ffd34eed17e7c5be537dcc.png)
【动手学深度学习】--18.图像增广
文章目录 图像增广1.常用的图像增广方法1.1翻转和裁剪1.2改变颜色1.3结合多种图像增广方法 2.使用图像增广进行训练3.训练 图像增广 官方笔记:图像增广 学习视频:数据增广【动手学深度学习v2】 图像增广在对训练图像进行一系列的随机变化之后ÿ…...
![](https://img-blog.csdnimg.cn/633b82685eee48b5811d2a9243d6a73b.png)
数据分析--统计学知识
描述型统计 描述统计 1.集中趋势 :众数、平均数、分位数 2.离散趋势: 极值(max)、极差(max-min)、平均差、方差、标准差、分位差 3.分布:峰泰、偏度 推理型统计 概率分布:离散型…...
![](https://img-blog.csdnimg.cn/b92d40571f164afd89ce38f4d556068e.png#pic_center)
matlab 计算点云协方差矩阵
目录 一、概述1、算法概述2、主要函数二、代码示例三、结果展示四、参数解析输入参数输出参数五、参考链接本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、概述...
![](https://www.ngui.cc/images/no-images.jpg)
python进阶之图像编程 pillow扩展库
一、概述 1.1pillow简介 Python Imaging Library (PIL)是python 下的图像处理模块,支持多种格式,并提供强大的图像处理功能,可以通过pip进行安装后使用。 1.2pillow具体应用 Pillow 库是 Python3 最常用的图像处理库,它支持多种图像格式&a…...
![](https://www.ngui.cc/images/no-images.jpg)
TiCDC Canal-JSON 消息接收示例(Java 版)
1.引言 业务程序经常会通过各式各样的缓存来提升用户的访问速度。 由于存在缓存,在一些实时性要求较高的场景中,需要在数据变更的同时将数据缓存进行更新或删除。 如果数据本身由其他业务部门提供,就无法在写入的同时做缓存的一致性处理。…...
![](https://img-blog.csdnimg.cn/img_convert/046e1b5c3ed89ba8faa8b28e207a052a.png)
SQLite、MySQL、PostgreSQL3个关系数据库之间的对比
引言 关系数据模型以行和列的表格形式组织数据,在数据库管理工具中占主导地位。今天还有其他数据模型,包括NoSQL和NewSQL,但是关系数据库管理系统(RDBMS)仍然占主导地位用于存储和管理全球数据。 本文比较了三种实现最…...
![](https://img-blog.csdnimg.cn/img_convert/e1b8a62fe5fcfb378364fc38fdd58d37.jpeg)
开源容灾备份软件,开源cdp备份软件
数据的安全性和完整性面临着硬件问题、黑客攻击、人为错误等各种威胁。在这种环境下,开源容灾备份软件应运而生,通过提供自动数据备份和恢复,有效地保证了公司的数据安全。 一、开源容灾备份软件的定义和作用 开源容灾备份软件是一种基于开源…...
![](https://www.ngui.cc/images/no-images.jpg)
Java合并区间
问题: 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 示例: 示例 1ÿ…...
![](https://www.ngui.cc/images/no-images.jpg)
前端面试:【代码质量与工程实践】单元测试、集成测试和持续集成
在现代软件开发中,确保代码质量是至关重要的。单元测试、集成测试和持续集成是关键的工程实践,用于提高代码的可靠性和可维护性。本文将深入探讨这些概念,以及它们如何在软件开发中发挥作用。 1. 单元测试(Unit Testing࿰…...
![](https://img-blog.csdnimg.cn/bed2a51fe27642ea9e21ebfc5006394c.png)
2023/8/17总结
项目完善: 算法推荐 item-CF 算法推荐我主要写的是协同过滤算法,然后协同过滤算法分成俩种—— 基于用户的 user-CF 基于物品的 item-CF 因为害怕用户冷启动,和数据量的原因 我选择了 item-CF 主要思路是——根据用户的点赞列表&…...
![](https://img-blog.csdnimg.cn/0577df4069494af19d2d7fe82eb71c41.png)
REDIS 7 教程 数据类型-进阶篇
⑥ *位图 bitmap 1. 理论 由0和1 状态表现的二进制位的bit 数组。 说明:用String 类型作为底层数据结构实现的一种统计二值状态的数据类型 位图本质是数组,它是基于String 数据类型的按位操作。该数组由多个二进制位组成,每个二进制位都对应一个偏…...
![](https://img-blog.csdnimg.cn/img_convert/d7b74195e18ace5710d9a7e29fc45358.png)
图文并茂:Python Tkinter从入门到高级实战全解析
目录 介绍什么是Tkinter?准备工作第一个Tkinter程序界面布局事件处理补充知识点 文本输入框复选框和单选框列表框弹出对话框 综合案例:待办事项列表总结 介绍 欢迎来到本篇文章,我们将带您深入了解如何在Python中使用Tkinter库来创建图形用…...
![](https://img-blog.csdnimg.cn/8278907884f245f595314d9363b984cd.png#pic_center)
npm和yarn的区别?
文章目录 前言npm和yarn的作用和特点npm和yarn的安装的机制npm安装机制yarn安装机制检测包解析包获取包链接包构建包 总结后言 前言 这一期给大家讲解npm和yarn的一些区别 npm和yarn的作用和特点 包管理:npm 和 yarn 可以用于安装、更新和删除 JavaScript 包。它们提…...
![](https://www.ngui.cc/images/no-images.jpg)
微服务项目容器编排docker-compose.yml、Dockerfile文件模板、相关配置文件、shell脚本
nacos Dockerfile(不需要特殊处理,使用docker conpose可以不写) # 基础镜像 FROM nacos/nacos-server # author MAINTAINER jianglifeng<jlifengfoxmail.com> RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ &&a…...
![](https://img-blog.csdnimg.cn/144f12009179409abf2a7904ca0b9a84.png)
算法通过村第三关-数组黄金笔记|数组难解
文章目录 前言数组中出现超过一半的数字数组中只出现一次的数字颜色的分类问题(荷兰国旗问题)基于冒泡排序的双指针(快慢指针)基于快排的双指针(对撞指针) 总结 前言 提示:苦不来自外在环境中的人、事、物,…...
![](https://www.ngui.cc/images/no-images.jpg)
【2023】LeetCode HOT 100——矩阵
目录 1. 矩阵置零1.1 C++实现1.2 Python实现1.3 时空分析2. 螺旋矩阵2.1 C++实现2.2 Python实现2.3 时空分析3. 旋转图像3.1 C++实现3.2 Python实现3.3 时空分析4. 搜索二维矩阵 II4.1 C++实现4.2 Python实现4.3 时空分析1. 矩阵置零 🔗 原题链接:...
![](https://img-blog.csdnimg.cn/7045376bbbec41aab43a2a4122344cd5.png)
springboot源码方法
利用LinkedHashSet移除List重复的数据protected final <T> List<T> removeDuplicates(List<T> list) {return new ArrayList<>(new LinkedHashSet<>(list));} SpringFactoriesLoader#loadFactoryNames 加载配置文件...
![](https://www.ngui.cc/images/no-images.jpg)
基于java街球社区网站设计与实现
摘 要 本文主要讲述了基于SpringBootVue模式的街球社区网站的设计与实现。这里所谓的街球社区网站是通过类似于百度贴吧之类的网上论坛使得所有的街球爱好者有一个可以互相交流的平台,并使所有用户可以在社区进行教学视频的观看以及相关体育运动产品的选购,平台的盈利主要靠…...
![](https://www.ngui.cc/images/no-images.jpg)
定时产生不同频率方波
/*----------------------------------------------- 内容:通过定时产生不同频率方波 ------------------------------------------------*/ #include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 /*-…...
![](https://img-blog.csdnimg.cn/0257a006bbc944c7b8923c23bc83f602.png)
Java“牵手”天猫商品sku信息API接口数据,天猫API接口申请指南
天猫平台商品sku属性信息接口是开放平台提供的一种API接口,通过调用API接口,开发者可以获取天猫商品的标题、价格、库存、月销量、总销量、库存、详情描述、图片等详细信息 。 获取商品销量接口API是一种用于获取电商平台上商品sku属性数据的接口&#…...
![](https://img-blog.csdnimg.cn/58451380d6cb4edabb75a6c6d1b799ba.png#pic_center)
【⑮MySQL | 视图】概述 | 创建 | 查看 | 更新 | 修改 | 删除
前言 ✨欢迎来到小K的MySQL专栏,本节将为大家带来MySQL视图概述 | 创建 | 查看 | 更新 | 修改 | 删除的分享✨ 目录 前言1.视图概述2.创建视图3.查看视图4.更新视图数据5.修改视图6.删除视图总结 1.视图概述 1.1 为什么使用视图? 视图一方面可以帮我们使…...
![](https://img-blog.csdnimg.cn/86c1971480a94912855514007bf3ad82.png)
Linux驱动开发一、RK3568把hello编译到Linux内核中运行。‘rk_vendor_read’未定义的引用
1、在字符设备目录下建立hello目录 ~/Linux/rk356x_linux/kernel/drivers/char/hello 2、进入hello目录,新建hello.c、Makefile、Kconfig三个文件 3、Kconfig是打开make menuconfig配置界面是后的选项,这Kconfig是在字符设备下的。 config HELLOtrist…...
![](https://www.ngui.cc/images/no-images.jpg)
enable_shared_from_this
用途: enable_shared_from_this 是一个基类模板,用于解决在类成员函数中获取类对象的 shared_ptr 的需求。它提供了一种机制,使类能够安全地从成员函数内部获得指向自身的 shared_ptr。 解决对象生命周期管理问题:在某些情况下&…...
![](/images/no-images.jpg)
网站套餐/好的竞价推广托管
JAVA和C都是面向对象的编程语言。都具有面向对象思想的特性(封装,继乘,多态)。但是因为c为兼容C,影响了其面向对象的彻底性!JAVA则是完全的面向对象语言,它句法更清晰,规模更小&…...
![](http://www.51test.space/wp-content/uploads/2017/06/1793.jpg)
用jsp做网站默认显示this is my jsp page/怎样推广app
使用jmeter进行压力测试时遇到一段时间后报内存溢出outfmenmory错误,导致jmeter卡死了,先尝试在jmeter.bat中增加了JVM_ARGS"-Xmx2048m -Xms2048m -Xmn256m -XX:PermSize128m -Xss256k",但结果运行时间增加了,但最终还是…...
![](https://www.oschina.net/img/hot3.png)
给企业做网站的公司/知乎营销推广
2019独角兽企业重金招聘Python工程师标准>>> $img $goods_info[goods_desc];//正则匹配获取img src属性中的地址$reg_tag <img.*?src"(.*?)">;preg_match_all($reg_tag,$img,$goods_info_img, PREG_SET_ORDER);// 处理urlforeach ($goods_info_…...
![](/images/no-images.jpg)
快速搭建外贸网站/免费网站大全
无线测试系统LitePointIQview提供强大工具,能取得并测量WiFi及蓝牙讯号,并以分析功能提供装置运作说明,简化了WiFi与Bluetooth装置的开发过程。IQview与IQflex製造解决方桉完全相容,使得以IQview开发的测试软体也能在已生产线使用…...
![](http://upload.chinaz.com/2012/0221/1329788502593.png)
网站推广专家/百度 seo排名查询
2003年4月7日,马云,在杭州,成立了一个神秘的组织。他叫来十位员工,要他们签了一份协议,这份协议要求他们立刻离开阿里巴巴,去做一个神秘的项目。这个项目要求绝对保密,老马戏称“连说梦话被老婆…...
![](https://img-blog.csdnimg.cn/20210223104543569.jpg#pic_center)
怎么用ai做企业网站框架/网络推广用什么软件好
合金体系的势函数除了eam势,还有meam势。在新版本的lammps中,meam势类型已经改为meam/c,本文主要介绍meam/c势的设置方法。 和普通的势函数文件不同,meam/c势有两个势函数文件:library.meam和**.meam,**表示…...