1.3.1 logback属性配置,在resources创建一个logback.xml
<configuration debug="false">
<appender name="INFO-FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${catalina.base}/webapps/myo2o/logs/info/info.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${catalina.base}/webapps/myo2o/logs/info/info.%d{yyyy-MM-dd}.%i.log.zipfileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>1024MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30maxHistory>
rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{55} -
%msg%n
pattern>
layout>
appender>
<appender name="ACCESS-FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>
${catalina.base}/webapps/myo2o/logs/access/access.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${catalina.base}/webapps/myo2o/logs/access/access.%d{yyyy-MM-dd}.%i.log.zipFileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>1024MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30maxHistory>
rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{55} -
%msg%n
pattern>
layout>
appender>
<logger name="com.imooc.myo2o" level="DEBUG" additivity="false">
<appender-ref ref="ACCESS-FILE" />
logger>
<root level="INFO">
<appender-ref ref="INFO-FILE" />
root>
configuration>
2.1.1 创建ShopDao接口
返回值为主键shop_id
public interface ShopDao {
// 新增店铺
int insertShop(Shop shop);
//更新店铺
int updateShop(Shop shop);
}
2.1.2 ShopDao.xml文件
<mapper namespace="com.imooc.o2o.dao.ShopDao">
<insert id="insertShop" useGeneratedKeys="true" keyColumn="shop_id" keyProperty="shopId">
insert into
tb_shop(owner_id,area_id,shop_category_id,shop_name,shop_desc,shop_addr,
phone,shop_img,priority,create_time,last_edit_time,enable_status,advice)
values
(#{owner.userId},#{area.areaId},#{shopCategory.shopCategoryId},#{shopName},#{shopDesc},
#{shopAddr},#{phone},#{shopImg},#{priority},#{createTime},#{lastEditTime},#{enableStatus},
#{advice})
insert>
<update id="updateShop" parameterType="com.imooc.o2o.entity.Shop">
update tb_shop
<set>
<if test="shopName != null">shop_name=#{shopName},if>
<if test="shopDesc != null">shop_desc=#{shopDesc},if>
<if test="shopAddr != null">shop_addr=#{shopAddr},if>
<if test="phone != null">phone=#{phone},if>
<if test="shopImg != null">shop_img=#{shopImg},if>
<if test="priority != null">priority=#{priority},if>
<if test="lastEditTime != null">last_edit_time=#{lastEditTime},if>
<if test="enableStatus != null">enable_status=#{enableStatus},if>
<if test="advice != null">advice=#{advice},if>
<if test="area != null">area_id=#{area.areaId},if>
<if test="shopCategory != null">shop_category_id=#{shopCategory.shopCategoryId},if>
set>
where shop_id=#{shopId}
update>
mapper>
3.1.1 添加图片处理的包
<dependency>
<groupId>net.coobirdgroupId>
<artifactId>thumbnailatorartifactId>
<version>0.4.8version>
dependency>
3.1.2 该包使用测试
public class ImageUtil {
public static void main(String[] args) throws IOException {
String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
Thumbnails.of(new File("D:\\TaoTao\\需要的图片\\images\\item\\shop\\15\\20170605234852321010.jpg"))
.size(200, 200)
.watermark(Positions.BOTTOM_RIGHT, ImageIO.read(new File(basePath+"\\watermark.png")), 0.25f)
.outputQuality(0.8)
.toFile("D:\\TaoTao\\需要的图片\\images\\item\\shop\\15\\watermark.png");
}
}
3.2.1 在util包下创建PathUtil类
public class PathUtil {
private static String seperator = System.getProperty("file.separator");
//返回项目图片的根路径
public static String getImgBasePath() {
//获取操作系统名称
String os = System.getProperty("os.name");
String basePath = "";
//如果是Windows系统
if(os.toLowerCase().startsWith("win")) {
basePath = "F:/project/image/";
}
else {
basePath = "/home/xiangze/image/";
}
//为了使符号/符合各个操作系统,将/替换成操作系统指定的分隔符
basePath = basePath.replace("/" , seperator);
return basePath;
}
//返回项目图片的子路径
public static String getShopImagePath(long shopId) {
String imagePath = "/upload/item/shop"+shopId+"/";
return imagePath.replace("/", seperator);
}
}
3.2.2 在util包下创建ImageUtil类
public class ImageUtil {
private static String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
private static final SimpleDateFormat sDateFormat = new SimpleDateFormat(
"yyyyMMddHHmmss"); // 时间格式化的格式
private static final Random r = new Random();
//处理缩略图
public static String generateThumbnail(CommonsMultipartFile thumbnail, String targetAddr) {
String realFileName = getRandomFileName();
String extension = getFileExtension(thumbnail);
makeDirPath(targetAddr);
String relativeAddr = targetAddr + realFileName + extension;
File dest = new File(PathUtil.getImgBasePath() + relativeAddr);
try {
Thumbnails.of(thumbnail.getInputStream()).size(200, 200).outputQuality(0.25f).toFile(dest);
} catch (IOException e) {
throw new RuntimeException("创建缩略图失败:" + e.toString());
}
return relativeAddr;
}
//生成随机文件名
public static String getRandomFileName() {
// 生成随机文件名:当前年月日时分秒+五位随机数(为了在实际项目中防止文件同名而进行的处理)
int rannum = (int) (r.nextDouble() * (99999 - 10000 + 1)) + 10000; // 获取随机数
String nowTimeStr = sDateFormat.format(new Date()); // 当前时间
return nowTimeStr + rannum;
}
//获取输入文件流的扩展名
private static String getFileExtension(CommonsMultipartFile cFile) {
String originalFileName = cFile.getOriginalFilename();
return originalFileName.substring(originalFileName.lastIndexOf("."));
}
//创建目标路径所涉及到的目录,比如com/immoc/o2o
//让其自动创建com、imooc,o2o三个文件夹
private static void makeDirPath(String targetAddr) {
String realFileParentPath = PathUtil.getImgBasePath() + targetAddr;
File dirPath = new File(realFileParentPath);
if (!dirPath.exists()) {
dirPath.mkdirs();
}
}
}
比如在操作店铺Shop类的时候,添加店铺是成功还是失败,我们需要有一个返回类型,卸载dto包中。返回给controller层去处理
public class ShopExecution {
//结果状态
private int state;
//状态标识
private String stateInfo;
//店铺数量
private int count;
//操作的店铺(增删改店铺时用到)
private Shop shop;
//shop列表(查询店铺的首使用)
private List<Shop> shopList;
public ShopExecution() {
}
//店铺操作失败的时候使用的构造器
public ShopExecution(ShopStateEnum stateEnum) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
}
//店铺操作成功的时候使用的构造器
public ShopExecution(ShopStateEnum stateEnum,Shop shop) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
this.shop = shop;
}
//店铺操作成功的时候使用的构造器
public ShopExecution(ShopStateEnum stateEnum,List<Shop> shopList) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
this.shopList = shopList;
}
}
public enum ShopStateEnum {
CHECK(0,"审核中"),
OFFLINE(1,"非法店铺"),
SUCCESS(1,"操作成功"),
PASS(2,"通过认证"),
INNER_ERROR(-1001,"内部系统错误"),
NULL_SHOPID(-1002,"shopId为空");
private int state;
private String stateInfo;
private ShopStateEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
//外部查找state的枚举类型
public static ShopStateEnum stateOf(int state) {
for(ShopStateEnum stateEnum:values()) {
if(stateEnum.getState()==state) {
return stateEnum;
}
}
return null;
}
public int getState() {
return state;
}
public String getStateInfo() {
return stateInfo;
}
}
public class ShopServiceImpl implements ShopService{
@Autowired
private ShopDao shopDao;
@Override
public ShopExecution addShop(Shop shop, CommonsMultipartFile shopImg) {
//验证店铺是否为空
if(shop==null) {
return new ShopExecution(ShopStateEnum.NULL_SHOP);
}
try {
//给店铺信息赋初始值
shop.setEnableStatus(0);
shop.setCreateTime(new Date());
shop.setLastEditTime(new Date());
//添加店铺信息
int effectedNum = shopDao.insertShop(shop);
if(effectedNum<=0) {
throw new RuntimeException("店铺创建失败");
}
else {
if(shopImg != null) {
//存储图片
try {
addShopImg(shop,shopImg);
} catch (Exception e) {
throw new RuntimeException("addShopImg error:"+e.getMessage());
}
//更新店铺的图片地址
effectedNum = shopDao.updateShop(shop);
if(effectedNum <= 0) {
throw new ShopOperationException("更新图片地址失败");
}
}
}
}catch(Exception e) {
throw new RuntimeException("addShop error:"+e.getMessage());
}
return null;
}
private void addShopImg(Shop shop, CommonsMultipartFile shopImg) {
//获取shop图片目录的相对路径
String dest = PathUtil.getShopImagePath(shop.getShopId());
String shopImgAddr = ImageUtil.generateThumbnail(shopImg, dest);
//修改shop中的的img地址
shop.setShopImg(shopImgAddr);
}
}
该类继承自RuntimeException 是为了保证事务的原子性,操作失败进行回滚。也就是如果添加到数据库的数据成功后抛出异常,也会将前面的操作进行回滚,清楚数据库的该条信息。
public class ShopOperationException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 4771094100507194189L;
public ShopOperationException(String msg) {
super(msg);
}
}
前端采用阿里巴巴的SUI mobile框架,源码的git地址https://github.com/sdc-alibaba/SUI-Mobile
由于html文件全部存放在WEB-INF目录下面,无法直接访问,所以要写相应的controller,让controller跳转到WEB-INF下的路径
7.1.1 测试controller
@Controller
@RequestMapping(value="shopadmin",method= {
RequestMethod.GET})
public class ShopAdminController {
@RequestMapping("/shopedit")
public String shopOperation() {
return "shop/shopedit";
}
}
用于注册店铺等
7.2.1 shopManagementController类
@Controller
@RequestMapping("/shopadmin")
public class ShopManagementController {
@Autowired
private ShopService shopService;
@RequestMapping(value="/registershop",method= {
RequestMethod.POST})
@ResponseBody
private Map<String,Object> registerShop(HttpServletRequest request) throws IOException{
Map<String,Object> modelMap = new HashMap<>();
//1.接受并转换相应的参数
String shopStr = HttpServletRequestUtil.getString(request, "shopStr");
ObjectMapper mapper = new ObjectMapper();
Shop shop = null;
try {
//这是将字符串shopStr转换成Shop.class类的JSON数据格式
shop = mapper.readValue(shopStr , Shop.class);
} catch (Exception e) {
modelMap.put("success", false);
modelMap.put("errMsg",e.getMessage());
return modelMap;
}
//获取前端传递的文件流(图片)
CommonsMultipartFile shopImg = null;
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(
request.getSession().getServletContext());
//判断上传的request是否含有图片,是就将图片赋值给shopImg
if(commonsMultipartResolver.isMultipart(request)) {
MultipartHttpServletRequest httpServletRequest = (MultipartHttpServletRequest) request;
shopImg = (CommonsMultipartFile) httpServletRequest.getFile("shopImg");
}
else {
modelMap.put("success", false);
modelMap.put("errMsg","上传图片不能为空");
return modelMap;
}
//2.注册店铺
if(shop!=null && shopImg!=null) {
PersonInfo owner = new PersonInfo();
//UserId是session传过来的
owner.setUserId(1L);
shop.setOwner(owner);
ShopExecution se = shopService.addShop(shop, shopImg.getInputStream(),shopImg.getOriginalFilename());
if(se.getState()==ShopStateEnum.CHECK.getState()) {
modelMap.put("success", true);
}
else {
modelMap.put("success", false);
modelMap.put("errMsg", se.getStateInfo());
}
}
else {
modelMap.put("success", false);
modelMap.put("errMsg","请输入店铺信息");
return modelMap;
}
//3.返回结果
return modelMap;
}
}
7.3.1 店铺注册页面跳转
首先我们需要通过url跳转到店铺注册页面,因为店铺注册页面的html文件是放在WEB-INF目录下的,不能直接访问,需要controller来实现跳转
@Controller
@RequestMapping(value="shopadmin",method= {
RequestMethod.GET})
public class ShopAdminController {
@RequestMapping("/shopedit")
public String shopOperation() {
return "shop/shopedit";
}
}
7.3.2 店铺注册前端shopedit.html代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>商店信息</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<link rel="shortcut icon" href="/favicon.ico">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="stylesheet"
href="//g.alicdn.com/msui/sm/0.6.2/css/sm.min.css">
<link rel="stylesheet"
href="//g.alicdn.com/msui/sm/0.6.2/css/sm-extend.min.css">
</head>
<body>
<header class="bar bar-nav">
<h1 class="title">商店信息</h1>
</header>
<div class="content">
<div class="list-block">
<ul>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-name"></i>
</div>
<div class="item-inner">
<div class="item-title label">商铺名称</div>
<div class="item-input">
<input type="text" id="shop-name" placeholder="商铺名称">
</div>
</div>
</div>
</li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email"></i>
</div>
<div class="item-inner">
<div class="item-title label">商铺分类</div>
<div class="item-input">
<select id="shop-category">
</select>
</div>
</div>
</div>
</li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email"></i>
</div>
<div class="item-inner">
<div class="item-title label">所属区域</div>
<div class="item-input">
<select id="area">
</select>
</div>
</div>
</div>
</li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email"></i>
</div>
<div class="item-inner">
<div class="item-title label">详细地址</div>
<div class="item-input">
<input type="text" id="shop-addr" placeholder="详细地址">
</div>
</div>
</div>
</li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email"></i>
</div>
<div class="item-inner">
<div class="item-title label">联系电话</div>
<div class="item-input">
<input type="tel" id="shop-phone" placeholder="联系电话">
</div>
</div>
</div>
</li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email"></i>
</div>
<div class="item-inner">
<div class="item-title label">缩略图</div>
<div class="item-input">
<input type="file" id="shop-img" placeholder="联系电话">
</div>
</div>
</div>
</li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email"></i>
</div>
<div class="item-inner">
<div class="item-title label">店铺简介</div>
<div class="item-input">
<textarea placeholder="店铺简介" id="shop-desc"></textarea>
</div>
</div>
</div>
</li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email"></i>
</div>
<div class="item-inner">
<label for="j_captcha" class="item-title label">验证码</label> <input
id="j_captcha" name="j_captcha" type="text"
class="form-control in" placeholder="验证码" />
<div class="item-input">
<img id="captcha_img" alt="点击更换" title="点击更换"
onclick="changeVerifyCode(this)" src="../Kaptcha" />
</div>
</div>
</div>
</li>
</ul>
</div>
<div class="content-block">
<div class="row">
<div class="col-50">
<a href="/myo2o/shop/shopmanage"
class="button button-big button-fill button-danger">返回</a>
</div>
<div class="col-50">
<a href="#" class="button button-big button-fill" id="submit">提交</a>
</div>
</div>
</div>
</div>
<script type='text/javascript'
src='//g.alicdn.com/sj/lib/zepto/zepto.js' charset='utf-8'></script>
<script type='text/javascript'
src='//g.alicdn.com/msui/sm/0.6.2/js/sm.min.js' charset='utf-8'></script>
<script type='text/javascript'
src='//g.alicdn.com/msui/sm/0.6.2/js/sm-extend.min.js' charset='utf-8'></script>
<script type='text/javascript'
src='../resources/js/common/commonutil.js' charset='utf-8'></script>
<script type='text/javascript' src='../resources/js/shop/shopedit.js'
charset='utf-8'></script>
</body>
</html>
7.3.3 店铺注册前端shopedit.js代码
$(function() {
var shopId = getQueryString('shopId');
var isEdit = shopId ? true : false;
var shopInfoUrl = '/myo2o/shop/getshopbyid?shopId=1';
// var shopInfoUrl = '/myo2o/shop/getshopbyid?shopId=' + shopId;
//用于获取店铺的info信息
var initUrl = '/o2o/shop/getshopinitinfo';
//注册店铺
var editShopUrl = '/o2o/shop/registershop';
if (isEdit) {
editShopUrl = '/myo2o/shop/modifyshop';
}
function getInfo(shopId) {
$.getJSON(shopInfoUrl, function(data) {
if (data.success) {
var shop = data.shop;
$('#shop-name').val(shop.shopName);
$('#shop-addr').val(shop.shopAddr);
$('#shop-phone').val(shop.phone);
$('#shop-desc').val(shop.shopDesc);
var shopCategory = '';
var tempAreaHtml = '';
data.areaList.map(function(item, index) {
tempAreaHtml += '';
});
$('#shop-category').html(shopCategory);
$('#shop-category').attr('disabled','disabled');
$('#area').html(tempAreaHtml);
$('#area').attr('data-id',shop.areaId);
}
});
}
//1. 获取从后台获取shopCategoryName和areaName,并显示到前台变量中
function getCategory() {
$.getJSON(initUrl, function(data) {
if (data.success) {
var tempHtml = '';
var tempAreaHtml = '';
data.shopCategoryList.map(function(item, index) {
tempHtml += '';
});
data.areaList.map(function(item, index) {
tempAreaHtml += '';
});
$('#shop-category').html(tempHtml);
$('#shop-category').removeAttr('disabled');
$('#area').html(tempAreaHtml);
}
});
}
alert(initUrl);
if (isEdit) {
getInfo(shopId);
} else {
getCategory();
}
//2. 该函数用于执行点击提交,就将前台数据提交到后台
$('#submit').click(function() {
var shop = {};
shop.shopName = $('#shop-name').val();
shop.shopAddr = $('#shop-addr').val();
shop.phone = $('#shop-phone').val();
shop.shopDesc = $('#shop-desc').val();
shop.shopCategory = {
shopCategoryId : $('#shop-category').find('option').not(function() {
return !this.selected;
}).data('id')
};
shop.area = {
areaId : $('#area').find('option').not(function() {
return !this.selected;
}).data('id')
};
//上传文件流
var shopImg = $("#shop-img")[0].files[0];
var formData = new FormData();
formData.append('shopImg', shopImg);
//上传shopStr字符流
formData.append('shopStr', JSON.stringify(shop));
var verifyCodeActual = $('#j_captcha').val();
if (!verifyCodeActual) {
$.toast('请输入验证码!');
return;
}
formData.append("verifyCodeActual", verifyCodeActual);
//将formData提交到后台
$.ajax({
url : editShopUrl,
type : 'POST',
// contentType: "application/x-www-form-urlencoded; charset=utf-8",
data : formData,
contentType : false,
processData : false,
cache : false,
success : function(data) {
if (data.success) {
$.toast('提交成功!');
if (isEdit){
$('#captcha_img').click();
} else{
window.location.href="/shop/shoplist";
}
} else {
$.toast('提交失败!');
$('#captcha_img').click();
}
}
});
});
});
对于从后台获取店铺类别和区域信息在下一节讲到。