体育商城项目总结 | 我的日常分享

体育商城项目总结

体育商城项目总结

[TOC]

一、项目概述

👉体育商城系统 https://git.yuencode.cn/jiaxiaoyu/sportshop

前台预览:http://sportshop.yuencode.cn/

账号:mike 123456

后台预览:http://sportshop-admin.yuencode.cn/

账号:admin 123456

1)技术架构

使用技术:

  • thymeleaf 3.0.11:渲染html动态页面。
  • servlet
  • mysql 5.7.26:数据库,存取数据。

使用框架:

系统架构:采用MVC设计模式MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。

  • Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。
  • View(视图) - 视图代表模型包含的数据的可视化。
  • Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。

img

2)系统用例图

image-20220620000808414

image-20220620001620219

image-20220620002754548

3)系统功能模块

image-20220620004152796

4)项目架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<sportshop>
├<front> ---前台
│ ├<src>
│ │ ├druid.properties ---数据库配置文件
│ │ ├<cn>
│ │ │ ├<yuencode>
│ │ │ │ ├<sportshop>
│ │ │ │ │ ├<dao> ---数据库操作层
│ │ │ │ │ ├<dto> ---数据传输对象
│ │ │ │ │ ├<pojo> ---JavaBean实体对象
│ │ │ │ │ ├<service> ---服务层,为Servlet层提供服务
│ │ │ │ │ ├<servlet> ---Servlet层,与页面进行直接交互
│ │ │ │ │ ├<util> ---工具类
│ ├<web>
│ │ ├<templates> ---Thymeleaf渲染的html模板
│ │ ├<upload> ---上传文件路径
│ │ ├<WEB-INF>
│ │ │ ├web.xml
│ │ │ ├<lib> ---依赖库
├<manage> ---后台
│ ├<src>
│ │ ├druid.properties ---数据库配置文件
│ │ ├<cn>
│ │ │ ├<yuencode>
│ │ │ │ ├<manage>
│ │ │ │ │ ├<dao> ---数据库操作层
│ │ │ │ │ ├<dto> ---数据传输对象
│ │ │ │ │ ├<filter> ---过滤器,对请求进行拦截放行
│ │ │ │ │ ├<pojo> ---JavaBean实体对象
│ │ │ │ │ ├<service> ---服务层,为Servlet层提供服务
│ │ │ │ │ ├<servlet> ---Servlet层,与页面进行直接交互
│ │ │ │ │ ├<util> ---工具类
│ ├<web>
│ │ ├<upload> ---上传文件路径
│ │ ├<WEB-INF>
│ │ │ ├web.xml
│ │ │ ├<lib> ---依赖库
│ │ │ ├<templates> ---Thymeleaf渲染的html模板

二、数据库设计

Diagram

1、t_admin管理员用户表

字段名 类型 长度 是否为NULL 注释
userid int 11 ID
username varchar 50 用户名
userpwd varchar 50 密码

2、t_catelog商品类别表

字段名 类型 长度 是否为NULL 注释
catelog_id int 11 分类ID
catelog_name varchar 50 类别名称
catelog_miaoshu varchar 50 类别描述

3、t_goods商品表

字段名 类型 长度 是否为NULL 注释
goods_id int 11 商品ID
goods_name varchar 20 商品名称
goods_miaoshu varchar 300 商品描述
goods_pic varchar 50 商品图片
market_price int 11 市面价格
mall_price int 11 商城价格
catelog_id int 11 分类编号
stock_num int 11 库存量
goods_address varchar 20 发货地
enter_date date 上架时间

4、t_order订单表

字段名 类型 长度 是否为NULL 注释
order_id varchar 50 订单编号
order_time datetime 下单时间
order_zhuangtai int 11 订单状态
order_jine int 11 订单总金额
order_address varchar 50 收货地址
order_pay varchar 20 支付方式
order_userid int 11 用户ID

5、t_orderitem订单商品项表

字段名 类型 长度 是否为NULL 注释
orderitem_id int 11 订单项ID
order_id varchar 50 订单编号
goods_id int 11 商品ID
goods_num int 11 商品数量

6、t_user会员用户表

字段名 类型 长度 是否为NULL 注释
user_id int 11 用户ID
user_name varchar 20 用户名
user_pwd varchar 30 密码
user_realname varchar 30 真实姓名
user_address varchar 50 用户地址
user_sex char 1 性别
user_tel char 11 电话
user_email varchar 30 邮箱
user_qq varchar 20 QQ

三、技术要点

3.1 登录验证

使用到了sessionfilter

原理:通过验证seesion中是否存在用户信息来识别用户是否登录。

难点1:@WebFilter("/*")通配符拦截所有请求,静态资源css、js、png等也会被拦截,需要对这些资源进行放行。

难点1解决方法:定义一个数组存放放行的请求,每次请求被过滤器拦截后,执行遍历数组操作,若包含放行请求则放行。

难点2:通过request.getRequestURL()request.getRequestURI()获取的请求路径只是返回的数据格式不同,请都包含请求参数,而项目中不同页面的访问常常使用action参数进行识别。

难点2解决方法:通过使用request.getQueryString()方法获取到请求参数,并拼接成完整的路径。

@WebFilter("/*")通配符拦截所有请求,静态资源css、js、png等也会被拦截。

过滤器完整代码:

LoginFilter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package cn.yuencode.manage.filter;

@WebFilter("/*")
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化");
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;

String requestURL = request.getQueryString() == null ? request.getRequestURL().toString() :
(request.getRequestURL().toString() + "?" + request.getQueryString());
// 排除登录路径 等资源
// 不需要过滤的url
String[] ignoreUrl = {".js", ".css", ".png", ".jpeg", ".jpg", ".ico",
".otf", "ttf", "svg", "index.do?action=toLogin","authCode.action","admin.do?action=login"};
boolean flag = true;
for (String s : ignoreUrl) {
if (requestURL.contains(s)) {
flag = false;
break;
}
}
if (flag) {
HttpSession session = request.getSession();
Object admin = session.getAttribute("admin");
if (null == admin) {
response.sendRedirect(request.getContextPath() + "/index.do?action=toLogin");
} else {
filterChain.doFilter(request, response);
}
} else {
filterChain.doFilter(request, response);
}
}

@Override
public void destroy() {

}
}

四、问题汇总

4.1 关于mysql中datetime类型字段的问题

在本地环境中,通过JdbcUtils工具类返回的maporder_time字段类型为Timestrap,如图4.1.1。

image-20220617225734609

图4.1.1

通过查看源码ResultSetImpl.classgetObject实现方法,发现它是根据得到的数据库字段类型,进行格式转换后存在Map中后再返回。

image-20220618000006116

图4.1.2

image-20220618000151702

图4.1.3

image-20220618000241947

图4.1.4

将项目打包部署到线上后出现如下图4.1.5异常,说明从JdbcUtil工具类中从数据库取出来的order_time格式为LocalDateTime

image-20220617230500488

图4.1.5

  • 2022.6.17 23.07未找到问题所在。

    • 已排除原因1:数据库原因已经被排除,在本地环境上使用线上的云数据库url=jdbc:mysql://106.13.204.178:3306/sportshop?useSSL=false&useServerPrepStmts=true,取出来order_time依然为Timestrap

    • 已排除原因2:Tomcat版本原因已经被排除,在本地与线下环境使用的版本均为apache-tomcat-8.5.78,问题依旧。

    • 未排除原因1:系统原因,不同系统下即使是同一版本的Tomcat可能存在不同,线上系统为CentOS 8.2.2004 x86_64

    • 疑点1:通过查看jdbc驱动源码ResultSetImpl.java其中并未有处理LocalDateTime的代码,按道理返回的map中的order_time的类型不可能为LocalDateTime类型。除非线上部署的jdbc驱动并未使用导入的mysql-connector-java-5.1.20.jar

  • 2022.6.17 23:50 调试了两天的问题4.1终于找到了原因所在。

    具体原因与上面疑点1的猜想是对的,因为之前在lib中放了两个不同版本的mysql驱动,如图4.1.7,可能在线上部署时,它调用的是mysql-connector-java-8.0.23.jar,而在本地环境中调用了是mysql-connector-java-5.1.20.jar

image-20220617235406839

图4.1.7

一看mysql-connector-java-8.0.23.jar的源码,果然它把数据库中的datetime转换为了LocalDateTime,如图4.1.8

image-20220617235648578

图4.1.8

将本地环境中的mysql-connector-java-5.1.20.jar只保留mysql-connector-java-8.0.23.jar如预期一样,出现了格式转换异常的问题。

(未发现问题原因之前的解决方案)最终解决方案,如抛出类型转换异常,对异常进行处理将LocalDateTime转换为Timestrap类型。

image-20220617225540214

4.2 JQuery动态生成插入的input元素,无法获取其value值

想实现点击发货按钮后,弹出对话框填写物流信息。如图4.2.1

image-20220618005103699

图4.2.1

通过使用pintuer框架中的对话框https://www.pintuer.com/documents/pintuer/1.x/javascript.html#dialog,发现弹出对话框中的写的<input>标签中value值获取一直为空字符串,查看文档发现没有其它任何说明。

看了一下js源码,发现它是将页面中写的对话框布局加上一些其它的布局后,再读取配置的标签属性,再去动态生成这个对话框的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$showdialogs=function(e){
var trigger=e.attr("data-toggle");
var getid=e.attr("data-target");
var data=e.attr("data-url");
var mask=e.attr("data-mask");
var width=e.attr("data-width");
var detail="";
var masklayout=$('<div class="dialog-mask"></div>');
if(width==null){width="80%";}

if (mask=="1"){
$("body").append(masklayout);
}
detail='<div class="dialog-win" style="position:fixed;width:'+width+';z-index:11;">';
if(getid!=null){detail=detail+$(getid).html();}
if(data!=null){detail=detail+$.ajax({url:data,async:false}).responseText;}
//alert(detail);
detail=detail+'</div>';

var win=$(detail);
win.find(".dialog").addClass("open");
$("body").append(win);
var x=parseInt($(window).width()-win.outerWidth())/2;
var y=parseInt($(window).height()-win.outerHeight())/2;
if (y<=10){y="10"}
win.css({"left":x,"top":y});
win.find(".dialog-close,.close").each(function(){
$(this).click(function(){
win.remove();
$('.dialog-mask').remove();
});
});
masklayout.click(function(){
win.remove();
$(this).remove();
});
};

后来发现,提交对话框表单还需要订单id字段。通过使用框架自带的逻辑无法实现将对应行订单的id传到对话框中去,边重写了框架方法,自定义了显示对话框的函数,自定义函数中通过对对话框的显示与隐藏实现,而不是移除对话框元素与动态生成对话框,所以就不存在动态生成的input元素无法获取其value值的问题了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 显示对话框
function showdialog(orderId){
let win = $(".dialog-win");
win.find("input[name=order_id]").val(orderId)
win.find(".dialog").addClass("open");
var masklayout=$('<div class="dialog-mask"></div>');
$("body").append(masklayout);
var x=parseInt($(window).width()-win.outerWidth())/2;
var y=parseInt($(window).height()-win.outerHeight())/2;
if (y<=10){y="10"}
win.css({"left":x,"top":y});

win.find(".dialog-close,.close").each(function(){
$(this).click(function(){
win.find(".dialog").removeClass("open")
$('.dialog-mask').remove();
});
});
masklayout.click(function(){
win.find(".dialog").removeClass("open")
$(this).remove();
});
}

$('#submitForm').click(function (){
let win = $(".dialog-win");
let logisticsSn = win.find("input[name=logisticsSn]").val()
if (logisticsSn){
$(".dialog-win form").submit()
}else {
alert("请填写物流单号")
}
})

4.3 使用pintuer表单验证后,提交无反应

问题描述:在商品添加页面中引入<script src="js/pintuer.js"></script>,使用了表单验证。添加必填验证的input框都为绿色验证通过,但是点击保存按钮无任何反应。

image-20220618011930650

问题原因:

在商品添加页面上为了回显错误提示消息,且让消息文本为红色,给提示的div标签添加了check-error属性,如下:

1
2
3
4
5
<div class="form-group">
<div class="check-error">
<div class="tips">[[${errMsg}]]</div>
</div>
</div>

通过查看pintuer.js源码如图4.3.1,发现check-error属性是用来进行表单验证不通过时的提示消息的class属性。而当提交表单时,会判断带check-error属性的标签数量,如果数量为0,则进行提交。我上面自己写了一个带check-errorclass属性的标签,自然就无法进行表单提交了。

image-20220618012702981

图4.3.1

4.4 获取参数值时,只能获取到表单提交过来的第一个键值对。

image-20220619232633458

image-20220619232612974

检查发现表单中添加了enctype="multipart/form-data"

image-20220619232734366

解决方式,要么去掉enctype="multipart/form-data"或者在Servlet中添加注解@MultipartConfig

五、项目收获

5.1 mysql数据库驱动中getObject方法的实现逻辑

通过获取数据库中的字段类型,根据类型转化成相应的类型后向上转型为Object在返回,具体源码在mysql驱动中com.mysql.jdbc.ResultSetImpl的实现类中。

5.2 响应文件提供给浏览器下载

注意要配置响应头Content-Disposition

Servlet方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 下载商品图片
*/
protected void downloadPic(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String picUrl = ParameterUtils.getStringParameter(request, "url", "");
String url = request.getServletContext().getRealPath(picUrl);
File file = new File(url);
ServletOutputStream outputStream = response.getOutputStream();
// 设置响应头,浏览器根据此响应头下载文件 URLEncoder.encode防止中文文件名乱码
response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(file.getName(), "UTF-8"));
InputStream in = new FileInputStream(file);
byte[] buf = new byte[1024 * 8];
int len = 0;
while ((len = in.read(buf)) != -1) {
outputStream.write(buf, 0, len);
}
outputStream.close();
in.close();
}

5.3 购物车数据存储

定义购物车的类Cart,包含cartItemMapLinkedHashMap的变量以键值对的形式存储购物项CartItem,在Cart类中定义添加商品、移除商品、清空购物车、获取总金额、获取商品列表等方法。

CartItem购物项中,包含商品信息Goods goods、商品数量Integer quantity、提供了其set、get方法,同时定义了获取商品总金额(商品数量*商品价格)的方法getTotalPrice()

Cart.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
* @author :贾小宇
* @program: sportshop
* @create :2022-06-18 21:53
* @description:购物车
*/
public class Cart {
private final Map<Integer, CartItem> cartItemMap = new LinkedHashMap<>();

/**
* 向购物车添加商品
*
* @param goods
* @param quantity
*/
public void addItem(Goods goods, Integer quantity) {
// 判断商品是否存在
Integer goods_id = goods.getGoods_id();
CartItem cartItem = cartItemMap.get(goods_id);
if (cartItem == null) {
cartItem = new CartItem();
cartItem.setGoods(goods);
cartItem.setQuantity(quantity);
// 添加到购物车中
cartItemMap.put(goods_id, cartItem);
} else {
// 更新购物项数量
cartItem.setQuantity(cartItem.getQuantity() + quantity);
}
}

/**
* 获取购物项的列表
*
* @return
*/
public List<CartItem> getCartItems() {
Collection<CartItem> values = cartItemMap.values();
return new ArrayList<>(values);
}

/**
* 获取购物车总金额
*/
public Integer getTotalPrice() {
Integer totalPrice = 0;
Collection<CartItem> cartItems = cartItemMap.values();
for (CartItem item : cartItems) {
totalPrice += item.getPrice();
}
return totalPrice;
}

/**
* 改变商品数量
*/
public boolean changeQuantity(Integer goodsId, Integer quantity) {
CartItem item = cartItemMap.get(goodsId);
item.setQuantity(quantity);
return true;
}

/**
* 清空购物车
*/
public boolean clearCart() {
cartItemMap.clear();
return true;
}

/**
* 移除购物项
*
* @param goodsId
* @return
*/
public boolean removeCartItem(Integer goodsId) {
cartItemMap.remove(goodsId);
return true;
}
}

CartItem.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* @author :贾小宇
* @program: sportshop
* @create :2022-06-18 21:53
* @description:购物项
*/
public class CartItem {
// 商品
private Goods goods;
// 商品数量
private Integer quantity;
// 购物项价格 商品数量*商品价格
private Integer price;

public Goods getGoods() {
return goods;
}

public void setGoods(Goods goods) {
this.goods = goods;
}

public Integer getQuantity() {
return quantity;
}

public void setQuantity(Integer quantity) {
this.quantity = quantity;
}

public Integer getPrice() {
return goods.getMall_price() * this.quantity;
}
}

5.4 多个数据库操作启动事务进行处理

在订购商品生成订单的业务功能中,需要对三个表t_ordert_orderitemt_goods,分别新增一条订单记录、将所有的订单项插入、更新商品的库存量。使用事务将三个操作合并成一个操作,一旦某个操作出现异常,则其他操作也同时不会生效,保证了操作的原子性、数据的完整性。

需要注意的是:

  1. 关闭自动提交
  2. 手动获取数据库连接,进行相关操作

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
@Override
public Integer insertOrder(Order order, List<Orderitem> orderitems) {
/*
使用事务方式执行sql语句
*/
Connection conn = null;
PreparedStatement pstmt = null;
try {
// 从连接池中获取连接
conn = JdbcUtil.getConnection();
// 关闭自动提交
conn.setAutoCommit(false);
// 1. 向t_order表插入一条记录
String sql1 = "INSERT INTO t_order(order_id,order_time,order_zhuangtai,order_jine,order_address,order_pay," +
"order_userid) VALUES(?,?,?,?,?,?,?)";
pstmt = conn.prepareStatement(sql1);
pstmt.setString(1, order.getOrder_id());
pstmt.setTimestamp(2, order.getOrder_time());
pstmt.setInt(3, order.getOrder_zhuangtai());
pstmt.setInt(4, order.getOrder_jine());
pstmt.setString(5, order.getOrder_address());
pstmt.setString(6, order.getOrder_pay());
pstmt.setInt(7, order.getUser().getUser_id());
pstmt.executeUpdate();
pstmt.close();

// 2. 向t_orderitem插入订单明细项
String sql2 = "INSERT INTO t_orderitem(order_id,goods_id,goods_num) " +
"VALUES(?,?,?)";
pstmt = conn.prepareStatement(sql2);
for (Orderitem item : orderitems) {
pstmt.setString(1, order.getOrder_id());
pstmt.setInt(2, item.getGoods().getGoods_id());
pstmt.setInt(3, item.getGoods_num());
// 添加到批处理
pstmt.addBatch();
}
// 执行批处理
pstmt.executeBatch();
pstmt.close();

// 3. 减少t_goods表中的商品库存量
String sql3 = "UPDATE t_goods SET stock_num=stock_num-? WHERE goods_id=?";
pstmt = conn.prepareStatement(sql3);
for (Orderitem item : orderitems) {
pstmt.setInt(1, item.getGoods_num());
pstmt.setInt(2, item.getGoods().getGoods_id());
// 添加到批处理
pstmt.addBatch();
}
// 执行批处理
pstmt.executeBatch();
pstmt.close();

//手动提交事务
conn.commit();
return 1;
} catch (SQLException e) {
e.printStackTrace();
// 抛出异常进行回滚
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
return -1;
} finally {
// 关闭资源
try {
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

5.5 数据库使用批处理执行sql

使用pstmt.addBatch()添加到批处理,当所有的语句都准备好了后,使用pstmt.executeBatch()执行批处理,最后关闭pstmt

1
2
3
4
5
6
7
8
9
10
11
String sql3 = "UPDATE t_goods SET stock_num=stock_num-? WHERE goods_id=?";
pstmt = conn.prepareStatement(sql3);
for (Orderitem item : orderitems) {
pstmt.setInt(1, item.getGoods_num());
pstmt.setInt(2, item.getGoods().getGoods_id());
// 添加到批处理
pstmt.addBatch();
}
// 执行批处理
pstmt.executeBatch();
pstmt.close();

5.6 状态值格式化的两种方法

  • 后端处理,在实体类中增加格式化状态值的方法,例如,订单的实体类中添加方法getOrderStatus(),方法中对private int order_zhuangtai状态的变量进行格式化后返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Order {
private String order_id; //订单编号
private Timestamp order_time; //下单日期
private int order_zhuangtai;//状态
private int order_jine;//总金额
private String order_address;//收货地址
private String order_pay;//付款方式
private User user; ////订单所属的用户 int order_uesrid;

//....get、set方法已省略....
public String getOrderStatus() {
switch (this.order_zhuangtai) {
case 1:
return "待发货";
case 2:
return "已发货";
case 3:
return "已完成";
default:
return "未知状态" + this.order_zhuangtai;
}
}
}

在页面中,直接调用getOrderStatus方法获取状态。

image-20220619215732800

  • 前端处理,在页面中通过js、模板引擎等对状态值进行判断输出。
1
2
3
4
5
6
<div th:switch="${item.order_zhuangtai}" >
<td th:case="1"><span class="tag">待发货</span></td>
<td th:case="2"><span class="tag bg-blue">已发货</span></td>
<td th:case="3"><span class="tag bg-green">已完成</span></td>
<td th:case="*"><span class="tag bg-red">未知状态[[${item.order_zhuangtai}]]</span></td>
</div>

5.7 Servlet上传文件

注意要在form标签添加enctype属性<form enctype="multipart/form-data"></form>与添加注解@MultipartConfig

若未添加属性enctype,则会抛出如图异常。

image-20220620005516014

若添加了enctype属性而未添加注解@MultipartConfig,则request.getParameter()获取不到值(从第二个开始)。

以下为实现文件上传的主要代码,其他逻辑代码被省略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@MultipartConfig
@WebServlet("/goods.do")
public class GoodsServlet extends ViewBaseServlet {
protected void add(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
Goods goods = new Goods();
Part goods_pic = request.getPart("goods_pic");
if (goods_pic == null || goods_pic.getSubmittedFileName().isEmpty()) {
request.setAttribute("errMsg", "请添加商品图片");
super.processTemplate("goods/add", request, response);
return;
}
String fileName = UUID.randomUUID().toString().replace("-", "");
String fileType = goods_pic.getSubmittedFileName().substring(goods_pic.getSubmittedFileName().lastIndexOf("."));
goods_pic.write(request.getServletContext().getRealPath("upload") + File.separator + fileName + fileType);
goods.setGoods_pic("upload/" + fileName + fileType);
goodsService.save(goods);
}
}

5.8 replace与replaceAll的区别

  • replace(char oldChar, char newChar):寓意为:返回一个新的字符串,它是通过用 newChar替换此字符串中出现的所有oldChar得到的。

  • replace(CharSequence target, CharSequence replacement):寓意为:使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。

  • replaceAll(String regex, String replacement):寓意为:使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。

  • replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

1
2
3
4
5
6
7
8
9
10
public class SportshopTest {
public static void main(String[] args) {
String s = UUID.randomUUID().toString();
System.out.println(s);
System.out.println(s.replace('-','#'));
System.out.println(s.replace("-", ""));
System.out.println(s.replaceAll("-", ""));
System.out.println(s.replaceFirst("-",""));
}
}

运行结果:

image-20220620011116111

image-20220620010950870

注意: replace与replaceAll都是替换所有的匹配值,replaceFirst才是仅替换第一个匹配值。只是replace的参数是char与CharSequence,而replaceAll参数为regex(正则表达式)与replacement。

六、项目运行效果

1、前台

image-20220620040744634

2、后台

image-20220620040813601

完成日期:2022年6月22日 jiaxiaoyu