效果展示
上传界面
上传成功响应
页面显示
项目环境说明
开发框架:spring boot 2 +themeleaf+MySQL8.5+JDBC JPA
开发语言及版本:Java1.8
开发工具:IntelliJ IDEA 2019+HBuilder X+Navicat
需求说明
- 用户可以批量上传图片
- 选择上传图片后会判断是否符合图片格式,图片大小是否合适。图片会显示在页面供用户浏览。
- 上传图片内容存放在项目目录下static下的photos下,并且图片路径存放在数据库中,多张图片用 ‘;’ 进行分隔。
- 能够从数据库中根据‘;’分隔符读出用户上传图片。
详细设计
- 项目目录及依赖说明
项目目录 说明:controller层是控制层,entity 是实体类层,repository相当于dao层,service是业务逻辑层,util是工具层,static里面存放的静态资源,templates里面是网页。
依赖导入,pom.xml 代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>renren</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>renren</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_renren?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
#display sql in console
spring.jpa.show-sql=true
#format json
spring.jackson.serialization.indent-output=true
server.port=8080
#数据库类型为mysql
spring.datasource.dbType=mysql
#启动时初始化5个连接
spring.datasource.initialSize=20
#最小空闲连接5个
spring.datasource.minIdle=5
#最大连接数量20
spring.datasource.maxActive=20
#获取连接等待时间60秒,超出报错
spring.datasource.maxWait=60000
#每60秒执行一次连接回收器
spring.datasource.timeBetweenEvictionRunsMillis=60000
#5分钟内没有任何操作的空闲连接会被回收
spring.datasource.minEvictableIdleTimeMillis=300000
#验证连接有效性的SQL
spring.datasource.validationQuery=select 'x'
#空闲时校验,建议开启
spring.datasource.testWhileIdle=true
#使用中是否校验有效性,推荐关闭
spring.datasource.testOnBorrow=false
#归还连接时校验有效性,推荐关闭
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=false
#设置过滤器,stat用于接收状态,wall用于防止SQL注入,logback则说明使用logback日志输出
spring.datasource.filters=stat,wall,logback
#统计所有数据源状态
spring.datasource.useGlobalDataSourceStat=true
#sql合并统计,与设置慢SQL时间为500毫秒
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#这里设置了静态资源的访问路径
server.servlet.context-path= /
spring.thymeleaf.prefix= classpath:/templates/
spring.thymeleaf.cache=false
spring.thymeleaf.suffix=.html
-
数据库设计
这里就不列表格了,直接截图navicat
-
前端页面设计
h5页面form表单代码
<form id="add_forum_msg" class="row" method="post" enctype="multipart/form-data">
<div class="form-group col-md-12">
<label for="type">专题</label>
<div class="form-controls">
<select name="forumType" class="form-control" id="type">
<option value="生活">生活</option>
<option value="学习">学习</option>
<option value="情感">情感</option>
<option value="经验">经验</option>
<option value="咨询">咨询</option>
</select>
</div>
</div>
<div class="form-group col-md-12">
<label for="forumTitle">标题</label>
<input type="text" id="forumHost" name="forumHost" th:value="${session.userId}" hidden/>
<input type="text" class="form-control" id="forumTitle" name="forumTitle" value="" placeholder="醒目的大字号[可总结性的文字]" />
</div>
<div class="form-group col-md-12">
<label for="forumDetail">描述</label>
<textarea class="form-control" id="forumDetail" name="forumDetail" rows="5" value="" placeholder="细节阐述[建议围绕标题展开]"></textarea>
</div>
<div class="form-group col-md-12">
<label for="contact">备注</label>
<input type="text" class="form-control" id="contact" name="contact" value="" placeholder="这些内容会显示在发布的底部[推荐联系方式]" />
</div>
<div class="form-group col-md-12">
<label>图片上传</label>
<div class="form-controls">
<ul class="list-inline clearfix lyear-uploads-pic">
<div class="upload-content">
<h3>上传图片</h3>
<div class="content-img">
<ul class="content-img-list"></ul>
<div class="file">
<i class="mdi mdi-plus fa-3x"></i>
<!-- <input type="file" name="imgs" accept="image/*" multiple>-->
<input type="file" name="imgs" accept="image/*" id="upload" multiple>
</div>
</div>
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
</div>
</div>
</div>
</div>
</ul>
</div>
</div>
<div class="form-group col-md-12">
<!-- <button type="submit" class="btn btn-primary " id="btn-submit-upload" >确 定</button>-->
<button type="submit" class="btn btn-primary" id="idsub" >确 定</button>
<button type="button" class="btn btn-default" onclick="javascript:history.back(-1);return false;">返 回</button>
</div>
</form>
对图片进行判断以及确认按钮添加监听(jQuery实现)
var imgFile = []; //文件流
var imgSrc = []; //图片路径
var imgName = []; //图片名字
//图片上传
$('#upload').on('change', function(e) {
var imgSize = this.files[0].size;
if (imgSize > 1024 * 2048 * 1) { //1M
return alert("上传图片不能超过2M");
};
if (this.files[0].type != 'image/png' && this.files[0].type != 'image/jpeg' && this.files[0].type != 'image/gif') {
return alert("图片上传格式不正确");
}
var imgBox = '.content-img-list';
var fileList = this.files;
for (var i = 0; i < fileList.length; i++) {
var imgSrcI = getObjectURL(fileList[i]);
imgName.push(fileList[i].name);
imgSrc.push(imgSrcI);
imgFile.push(fileList[i]);
console.log("#upload------>"+imgName+imgFile);
}
addNewContent(imgBox);
this.value = null; //上传相同图片
});
$("#idsub").on('click', function() {
console.log("js——————->#idsub的点击事件被执行");
var formData = new FormData($("#add_forum_msg")[0]);
$.each(imgFile, function (i, file) {
// formFile.append('myFile[]', file);
formData.append('myFile[]', file);
console.log("myfile[]-------->"+file);
});
$.ajax({
type : "POST",
url : "/user/launch_oneForum" , //变量
//方法1:将form表单数据序列化
// data : $('#add_forum_msg').serializeArray(),
data: formData,
async : false, // 为true会继续执行ajax后面的脚本,直到服务器端返回数据后,触发$.ajax里的success方法,这时候执行的是两个线程。
cache: false,
processData : false, //必须false才会避开jQuery对 formdata 的默认处理
contentType : false, //必须false才会自动加上正确的Content-Type
error : function() {//请求失败处理函数
alert("原始数据错误!");
console.log($('#add_forum_msg').serializeArray());
},
success :function(data) {//result结果
console.log("------>data:"+data);
if (data.msg == "success") {
$.confirm({
title: 'SUCCESS!',
content: '发布成功!',
type: 'green',
buttons: {
omg: {
text: '好的',
btnClass: 'btn-green',
},
close: {
text: '关闭',
}
}
});
}
if (data.msg == "addExcept") {
$.confirm({
title: 'ADDEXCEPTION',
content: '发布失败!',
type: 'danger',
buttons: {
omg: {
text: '好的',
btnClass: 'btn-red',
},
close: {
text: '关闭',
}
}
});
}
if (data.msg == "uploadExcept") {
$.confirm({
title: 'UPLOADEXCEPTION',
content: '图片上传失败!',
type: 'warning',
buttons: {
omg: {
text: '好的',
btnClass: 'btn-red',
},
close: {
text: '关闭',
}
}
});
}
}
})
return false;
});
- 后台功能实现
实体类ForumLaunch,如下,get,set方法未码出。
@Entity
@Table(name="forum_launch_info")
public class ForumLaunch {
@Id
private String forumId;
private String forumTitle;
private String forumType;
private String forumDetail;
private String forumImg;
@Column(name="launch_time")
private Date launchTime;
private String contact;
private String launchCheck;
private String forumHost;
}
实体类的封装类ForumLaunchFlag如下,get,set方法未码出。
@Configuration
public class ForumLaunchFlag {
private List<ForumLaunch>forumLaunchList;//存储信息
private String flag;//标识
private Page<ForumLaunch>forumLaunchPage;//分页查询
}
repository类 ForumLaunchRepository 代码如下
public interface ForumLaunchRepository extends JpaRepository<ForumLaunch,String>, JpaSpecificationExecutor<ForumLaunch> {
//这里可以自己写方法
}
service 层 ForumService代码如下:
public interface ForumService {
/*论坛发布的方法,
返回值为ForumLaunchFlag类型
*/
ForumLaunchFlag launchOneService(ForumLaunch forumLaunch);//发布一则论坛
实现接口类 ForumServiceImpl代码如下:
@Service("ForumService")
public class ForumServiceImpl implements ForumService {
@Resource
private ForumLaunchRepository forumLaunchRepository;
@Resource
private ForumLaunchFlag forumLaunchFlag;
public ForumLaunchFlag launchOneService(ForumLaunch forumLaunch) {
forumLaunchFlag.setFlag(FlagMsg.Success());
try{
forumLaunchRepository.save(forumLaunch);
}catch (Exception e){
System.out.println("ForumService:add------>"+e);
forumLaunchFlag.setFlag(FlagMsg.ExceptionError());
}
return forumLaunchFlag;
}
controller层 forumController 代码如下:
@Controller
@RequestMapping("/user")
public class ForumController {
private ForumLaunchFlag forumLaunchFlag;
@Resource
private ForumService forumService;
/*页面点击事件响应:
位置:论坛页面左侧边栏---发布我的
结果:跳转到论坛“发布我的”的页面*/
@RequestMapping("/forum_launch.html")
public ModelAndView a_click_forumLunch(){
ModelAndView mav = new ModelAndView();
mav.setViewName("forum_launch");
return mav;
}
/*发布一则论坛
返回判断标识
*/
@ResponseBody //用于返回json数据,用ajax时
@PostMapping("/launch_oneForum")
public Map<String, Object> but_launch_one_forum(ForumLaunch forumLaunch,@RequestParam("myFile[]") MultipartFile[] files,HttpServletRequest request){
String msg="";
//得到的whologin是登录状态下的存入session类的“userId”键值对的值
String whoLogin = (String)request.getSession().getAttribute("userId");
String putInMySql = "";
int length = files.length;
System.out.println("controller---length:"+length);
for (MultipartFile file : files) { //循环保存文件
String fileName = file.getOriginalFilename();
String suffixName = fileName.substring(fileName.lastIndexOf("."));
//重新生成文件名
fileName = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + UUID.randomUUID() + suffixName;
putInMySql += "/photos/"+fileName+";";
// 存放上传图片的文件夹
File fileDir = UploadUtils.getImgDirFile();
System.out.println(fileDir.getAbsolutePath());
try {
// 构建真实的文件路径
File newFile = new File(fileDir.getAbsolutePath() + File.separator + fileName);
System.out.println(newFile.getAbsolutePath());
// 上传图片到 -》 “绝对路径”
file.transferTo(newFile);
} catch (IOException e) {
e.printStackTrace();
msg = "uploadExcept";
}
}
//forumLaunch.setForumId(AutoGenerateId.getOrderId("many"));//主键生成工具下生成的,测试可自己输入字符串
forumLaunch.setForumId("1");
forumLaunch.setForumHost("蓝鸢尾");
forumLaunch.setForumImg(putInMySql);
forumLaunch.setLaunchTime(new Date());
//写入数据库
forumLaunchFlag = forumService.launchOneService(forumLaunch);
//判断
if (FlagMsg.Success() == forumLaunchFlag.getFlag()) {
msg = "success";
} else {
msg = "addExcept";
}
Map<String,Object> map = new HashMap<>();
map.put("msg",msg);
return map;
}
总结
spring boot是一个很好用的web开发框架,是一个基于注解的开发模式,想要学的更深入就需要对它的注解更加熟练。没事看看它的接口,学习其它大佬的代码编写思想。
当然,我也是个spring boot的初学者,代码编写还不够规范,开发能力也急需加强,开发小白,不请自来,希望能够共同探讨问题,也会持续进行项目开发笔记更新。
代码是从工程下面截下来的,有差错欢迎留言指正,没有写过多的说明。
附录:用到的一些自定义工具类:
FlagMsg 代码如下:
@Configuration
public class FlagMsg {//标识字符串
public static String pwdNotMatch(){
return "两次密码输入不一致";
}
public static String pwdWrong(){
return "密码错误";
}
public static String IsNotExist(){
return "不存在";
}
public static String IsExist(){
return "已存在";
}
public static String ExceptionError(){
return "意料之外的错误";
}
public static String IsNull(){
return "为空";
}
public static String Success(){
return "通过的";
}
public static String Error(){
return "失败的";
}
}
AutoGenerateId 代码如下:
```java
/**
* 按照时间理论生成唯一属性ID
* 方法getOrderId默认生成的是时间加3位随机数数的号码,即:
* yyyyMMddHHmmssXXX
*/
public class AutoGenerateId {
/**
* 20位末尾的数字id
*/
public static int Guid=100;
private static AutoGenerateId GetTime;
public static String getOrderId(String ManyOrBitAndDefaultBit) {//默认生成的是时间加3位随机数数的号码
GetTime.Guid+=1;
long now = System.currentTimeMillis();
//获取时间
SimpleDateFormat dateFormat=new SimpleDateFormat("yyyyMMddHHmmss");
//获取时间戳
String time=dateFormat.format(now);
String info=now+"";
//获取三位随机数
//int ran=(int) ((Math.random()*9+1)*100);
//要是一段时间内的数据连过大会有重复的情况,所以做以下修改
int ran=0;
if(GetTime.Guid>999){
GetTime.Guid=100;
}
ran=GetTime.Guid;
if("many"==ManyOrBitAndDefaultBit){
return time+ran+info.substring(2, info.length());
}
return time+ran;
}
}
UploadUtils 代码如下:
public class UploadUtils {
// 项目根路径下的目录 -- SpringBoot static 目录相当于是根路径下(SpringBoot 默认)
public final static String IMG_PATH_PREFIX = "static/photos";
public static File getImgDirFile(){
// 构建上传文件的存放 "文件夹" 路径
String fileDirPath = new String("src/main/resources/" + IMG_PATH_PREFIX);
File fileDir = new File(fileDirPath);
if(!fileDir.exists()){
// 递归生成文件夹
fileDir.mkdirs();
}
return fileDir;
}
}