应用程序主函数接口
@SpringBootApplication(scanBasePackages = {"org.linlinjava.litemall.db", "org.linlinjava.litemall.core", "org.linlinjava.litemall.admin"})
@MapperScan("org.linlinjava.litemall.db.dao")
@EnableTransactionManagement
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
优惠卷管理
/**
* 检测优惠券过期情况
*/
@Component
public class CouponJob {
private final Log logger = LogFactory.getLog(CouponJob.class);
@Autowired
private LitemallCouponService couponService;
@Autowired
private LitemallCouponUserService couponUserService;
/**
* 每隔一个小时检查
* TODO
* 注意,因为是相隔一个小时检查,因此导致优惠券真正超时时间可能比设定时间延迟1个小时
*/
@Scheduled(fixedDelay = 60 * 60 * 1000)
public void checkCouponExpired() {
logger.info("系统开启任务检查优惠券是否已经过期");
List<LitemallCoupon> couponList = couponService.queryExpired();
for(LitemallCoupon coupon : couponList){
coupon.setStatus(CouponConstant.STATUS_EXPIRED);
couponService.updateById(coupon);
}
List<LitemallCouponUser> couponUserList = couponUserService.queryExpired();
for(LitemallCouponUser couponUser : couponUserList){
couponUser.setStatus(CouponUserConstant.STATUS_EXPIRED);
couponUserService.update(couponUser);
}
}
}
订单管理
/**
* 检测订单状态
*/
@Component
public class OrderJob {
private final Log logger = LogFactory.getLog(OrderJob.class);
@Autowired
private LitemallOrderGoodsService orderGoodsService;
@Autowired
private LitemallOrderService orderService;
@Autowired
private LitemallGoodsProductService productService;
/**
* 自动取消订单
* <p>
* 定时检查订单未付款情况,如果超时 LITEMALL_ORDER_UNPAID 分钟则自动取消订单
* 定时时间是每次相隔半个小时。
* <p>
* TODO
* 注意,因为是相隔半小时检查,因此导致订单真正超时时间是 [LITEMALL_ORDER_UNPAID, 30 + LITEMALL_ORDER_UNPAID]
*/
@Scheduled(fixedDelay = 30 * 60 * 1000)
@Transactional
public void checkOrderUnpaid() {
logger.info("系统开启任务检查订单是否已经超期自动取消订单");
List<LitemallOrder> orderList = orderService.queryUnpaid(SystemConfig.getOrderUnpaid());
for (LitemallOrder order : orderList) {
// 设置订单已取消状态
order.setOrderStatus(OrderUtil.STATUS_AUTO_CANCEL);
order.setEndTime(LocalDateTime.now());
if (orderService.updateWithOptimisticLocker(order) == 0) {
throw new RuntimeException("更新数据已失效");
}
// 商品货品数量增加
Integer orderId = order.getId();
List<LitemallOrderGoods> orderGoodsList = orderGoodsService.queryByOid(orderId);
for (LitemallOrderGoods orderGoods : orderGoodsList) {
Integer productId = orderGoods.getProductId();
Short number = orderGoods.getNumber();
if (productService.addStock(productId, number) == 0) {
throw new RuntimeException("商品货品库存增加失败");
}
}
logger.info("订单 ID=" + order.getId() + " 已经超期自动取消订单");
}
}
/**
* 自动确认订单
* <p>
* 定时检查订单未确认情况,如果超时 LITEMALL_ORDER_UNCONFIRM 天则自动确认订单
* 定时时间是每天凌晨3点。
* <p>
* TODO
* 注意,因为是相隔一天检查,因此导致订单真正超时时间是 [LITEMALL_ORDER_UNCONFIRM, 1 + LITEMALL_ORDER_UNCONFIRM]
*/
@Scheduled(cron = "0 0 3 * * ?")
public void checkOrderUnconfirm() {
logger.info("系统开启任务检查订单是否已经超期自动确认收货");
List<LitemallOrder> orderList = orderService.queryUnconfirm(SystemConfig.getOrderUnconfirm());
for (LitemallOrder order : orderList) {
// 设置订单已取消状态
order.setOrderStatus(OrderUtil.STATUS_AUTO_CONFIRM);
order.setConfirmTime(LocalDateTime.now());
if (orderService.updateWithOptimisticLocker(order) == 0) {
logger.info("订单 ID=" + order.getId() + " 数据已经更新,放弃自动确认收货");
} else {
logger.info("订单 ID=" + order.getId() + " 已经超期自动确认收货");
}
}
}
/**
* 可评价订单商品超期
* <p>
* 定时检查订单商品评价情况,如果确认商品超时 LITEMALL_ORDER_COMMENT 天则取消可评价状态
* 定时时间是每天凌晨4点。
* <p>
* TODO
* 注意,因为是相隔一天检查,因此导致订单真正超时时间是 [LITEMALL_ORDER_COMMENT, 1 + LITEMALL_ORDER_COMMENT]
*/
@Scheduled(cron = "0 0 4 * * ?")
public void checkOrderComment() {
logger.info("系统开启任务检查订单是否已经超期未评价");
LocalDateTime now = LocalDateTime.now();
List<LitemallOrder> orderList = orderService.queryComment(SystemConfig.getOrderComment());
for (LitemallOrder order : orderList) {
order.setComments((short) 0);
orderService.updateWithOptimisticLocker(order);
List<LitemallOrderGoods> orderGoodsList = orderGoodsService.queryByOid(order.getId());
for (LitemallOrderGoods orderGoods : orderGoodsList) {
orderGoods.setComment(-1);
orderGoodsService.updateById(orderGoods);
}
}
}
}
上架商品管理服务
@Service
public class AdminGoodsService {
private final Log logger = LogFactory.getLog(AdminGoodsService.class);
@Autowired
private LitemallGoodsService goodsService;
@Autowired
private LitemallGoodsSpecificationService specificationService;
@Autowired
private LitemallGoodsAttributeService attributeService;
@Autowired
private LitemallGoodsProductService productService;
@Autowired
private LitemallCategoryService categoryService;
@Autowired
private LitemallBrandService brandService;
@Autowired
private LitemallCartService cartService;
@Autowired
private LitemallOrderGoodsService orderGoodsService;
@Autowired
private QCodeService qCodeService;
public Object list(String goodsSn, String name,
Integer page, Integer limit, String sort, String order) {
List<LitemallGoods> goodsList = goodsService.querySelective(goodsSn, name, page, limit, sort, order);
return ResponseUtil.okList(goodsList);
}
private Object validate(GoodsAllinone goodsAllinone) {
LitemallGoods goods = goodsAllinone.getGoods();
String name = goods.getName();
if (StringUtils.isEmpty(name)) {
return ResponseUtil.badArgument();
}
String goodsSn = goods.getGoodsSn();
if (StringUtils.isEmpty(goodsSn)) {
return ResponseUtil.badArgument();
}
// 品牌商可以不设置,如果设置则需要验证品牌商存在
Integer brandId = goods.getBrandId();
if (brandId != null && brandId != 0) {
if (brandService.findById(brandId) == null) {
return ResponseUtil.badArgumentValue();
}
}
// 分类可以不设置,如果设置则需要验证分类存在
Integer categoryId = goods.getCategoryId();
if (categoryId != null && categoryId != 0) {
if (categoryService.findById(categoryId) == null) {
return ResponseUtil.badArgumentValue();
}
}
LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes();
for (LitemallGoodsAttribute attribute : attributes) {
String attr = attribute.getAttribute();
if (StringUtils.isEmpty(attr)) {
return ResponseUtil.badArgument();
}
String value = attribute.getValue();
if (StringUtils.isEmpty(value)) {
return ResponseUtil.badArgument();
}
}
LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications();
for (LitemallGoodsSpecification specification : specifications) {
String spec = specification.getSpecification();
if (StringUtils.isEmpty(spec)) {
return ResponseUtil.badArgument();
}
String value = specification.getValue();
if (StringUtils.isEmpty(value)) {
return ResponseUtil.badArgument();
}
}
LitemallGoodsProduct[] products = goodsAllinone.getProducts();
for (LitemallGoodsProduct product : products) {
Integer number = product.getNumber();
if (number == null || number < 0) {
return ResponseUtil.badArgument();
}
BigDecimal price = product.getPrice();
if (price == null) {
return ResponseUtil.badArgument();
}
String[] productSpecifications = product.getSpecifications();
if (productSpecifications.length == 0) {
return ResponseUtil.badArgument();
}
}
return null;
}
/**
* 编辑商品
* <p>
* TODO
* 目前商品修改的逻辑是
* 1. 更新litemall_goods表
* 2. 逻辑删除litemall_goods_specification、litemall_goods_attribute、litemall_goods_product
* 3. 添加litemall_goods_specification、litemall_goods_attribute、litemall_goods_product
* <p>
* 这里商品三个表的数据采用删除再添加的策略是因为
* 商品编辑页面,支持管理员添加删除商品规格、添加删除商品属性,因此这里仅仅更新是不可能的,
* 只能删除三个表旧的数据,然后添加新的数据。
* 但是这里又会引入新的问题,就是存在订单商品货品ID指向了失效的商品货品表。
* 因此这里会拒绝管理员编辑商品,如果订单或购物车中存在商品。
* 所以这里可能需要重新设计。
*/
@Transactional
public Object update(GoodsAllinone goodsAllinone) {
Object error = validate(goodsAllinone);
if (error != null) {
return error;
}
LitemallGoods goods = goodsAllinone.getGoods();
LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes();
LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications();
LitemallGoodsProduct[] products = goodsAllinone.getProducts();
Integer id = goods.getId();
//将生成的分享图片地址写入数据库
String url = qCodeService.createGoodShareImage(goods.getId().toString(), goods.getPicUrl(), goods.getName());
goods.setShareUrl(url);
// 商品基本信息表litemall_goods
if (goodsService.updateById(goods) == 0) {
throw new RuntimeException("更新数据失败");
}
Integer gid = goods.getId();
specificationService.deleteByGid(gid);
attributeService.deleteByGid(gid);
productService.deleteByGid(gid);
// 商品规格表litemall_goods_specification
for (LitemallGoodsSpecification specification : specifications) {
specification.setGoodsId(goods.getId());
specificationService.add(specification);
}
// 商品参数表litemall_goods_attribute
for (LitemallGoodsAttribute attribute : attributes) {
attribute.setGoodsId(goods.getId());
attributeService.add(attribute);
}
// 商品货品表litemall_product
for (LitemallGoodsProduct product : products) {
product.setGoodsId(goods.getId());
productService.add(product);
}
return ResponseUtil.ok();
}
@Transactional
public Object delete(LitemallGoods goods) {
Integer id = goods.getId();
if (id == null) {
return ResponseUtil.badArgument();
}
Integer gid = goods.getId();
goodsService.deleteById(gid);
specificationService.deleteByGid(gid);
attributeService.deleteByGid(gid);
productService.deleteByGid(gid);
return ResponseUtil.ok();
}
@Transactional
public Object create(GoodsAllinone goodsAllinone) {
Object error = validate(goodsAllinone);
if (error != null) {
return error;
}
LitemallGoods goods = goodsAllinone.getGoods();
LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes();
LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications();
LitemallGoodsProduct[] products = goodsAllinone.getProducts();
String name = goods.getName();
if (goodsService.checkExistByName(name)) {
return ResponseUtil.fail(GOODS_NAME_EXIST, "商品名已经存在");
}
// 商品基本信息表litemall_goods
goodsService.add(goods);
//将生成的分享图片地址写入数据库
String url = qCodeService.createGoodShareImage(goods.getId().toString(), goods.getPicUrl(), goods.getName());
if (!StringUtils.isEmpty(url)) {
goods.setShareUrl(url);
if (goodsService.updateById(goods) == 0) {
throw new RuntimeException("更新数据失败");
}
}
// 商品规格表litemall_goods_specification
for (LitemallGoodsSpecification specification : specifications) {
specification.setGoodsId(goods.getId());
specificationService.add(specification);
}
// 商品参数表litemall_goods_attribute
for (LitemallGoodsAttribute attribute : attributes) {
attribute.setGoodsId(goods.getId());
attributeService.add(attribute);
}
// 商品货品表litemall_product
for (LitemallGoodsProduct product : products) {
product.setGoodsId(goods.getId());
productService.add(product);
}
return ResponseUtil.ok();
}
public Object list2() {
// http://element-cn.eleme.io/#/zh-CN/component/cascader
// 管理员设置“所属分类”
List<LitemallCategory> l1CatList = categoryService.queryL1();
List<CatVo> categoryList = new ArrayList<>(l1CatList.size());
for (LitemallCategory l1 : l1CatList) {
CatVo l1CatVo = new CatVo();
l1CatVo.setValue(l1.getId());
l1CatVo.setLabel(l1.getName());
List<LitemallCategory> l2CatList = categoryService.queryByPid(l1.getId());
List<CatVo> children = new ArrayList<>(l2CatList.size());
for (LitemallCategory l2 : l2CatList) {
CatVo l2CatVo = new CatVo();
l2CatVo.setValue(l2.getId());
l2CatVo.setLabel(l2.getName());
children.add(l2CatVo);
}
l1CatVo.setChildren(children);
categoryList.add(l1CatVo);
}
// http://element-cn.eleme.io/#/zh-CN/component/select
// 管理员设置“所属品牌商”
List<LitemallBrand> list = brandService.all();
List<Map<String, Object>> brandList = new ArrayList<>(l1CatList.size());
for (LitemallBrand brand : list) {
Map<String, Object> b = new HashMap<>(2);
b.put("value", brand.getId());
b.put("label", brand.getName());
brandList.add(b);
}
Map<String, Object> data = new HashMap<>();
data.put("categoryList", categoryList);
data.put("brandList", brandList);
return ResponseUtil.ok(data);
}
public Object detail(Integer id) {
LitemallGoods goods = goodsService.findById(id);
List<LitemallGoodsProduct> products = productService.queryByGid(id);
List<LitemallGoodsSpecification> specifications = specificationService.queryByGid(id);
List<LitemallGoodsAttribute> attributes = attributeService.queryByGid(id);
Integer categoryId = goods.getCategoryId();
LitemallCategory category = categoryService.findById(categoryId);
Integer[] categoryIds = new Integer[]{};
if (category != null) {
Integer parentCategoryId = category.getPid();
categoryIds = new Integer[]{parentCategoryId, categoryId};
}
Map<String, Object> data = new HashMap<>();
data.put("goods", goods);
data.put("specifications", specifications);
data.put("products", products);
data.put("attributes", attributes);
data.put("categoryIds", categoryIds);
return ResponseUtil.ok(data);
}
}
订单管理服务
@Service
public class AdminOrderService {
private final Log logger = LogFactory.getLog(AdminOrderService.class);
@Autowired
private LitemallOrderGoodsService orderGoodsService;
@Autowired
private LitemallOrderService orderService;
@Autowired
private LitemallGoodsProductService productService;
@Autowired
private LitemallUserService userService;
@Autowired
private LitemallCommentService commentService;
@Autowired
private WxPayService wxPayService;
@Autowired
private NotifyService notifyService;
@Autowired
private LogHelper logHelper;
public Object list(Integer userId, String orderSn, List<Short> orderStatusArray,
Integer page, Integer limit, String sort, String order) {
List<LitemallOrder> orderList = orderService.querySelective(userId, orderSn, orderStatusArray, page, limit, sort, order);
return ResponseUtil.okList(orderList);
}
public Object detail(Integer id) {
LitemallOrder order = orderService.findById(id);
List<LitemallOrderGoods> orderGoods = orderGoodsService.queryByOid(id);
UserVo user = userService.findUserVoById(order.getUserId());
Map<String, Object> data = new HashMap<>();
data.put("order", order);
data.put("orderGoods", orderGoods);
data.put("user", user);
return ResponseUtil.ok(data);
}
/**
* 订单退款
* <p>
* 1. 检测当前订单是否能够退款;
* 2. 微信退款操作;
* 3. 设置订单退款确认状态;
* 4. 订单商品库存回库。
* <p>
* TODO
* 虽然接入了微信退款API,但是从安全角度考虑,建议开发者删除这里微信退款代码,采用以下两步走步骤:
* 1. 管理员登录微信官方支付平台点击退款操作进行退款
* 2. 管理员登录litemall管理后台点击退款操作进行订单状态修改和商品库存回库
*
* @param body 订单信息,{ orderId:xxx }
* @return 订单退款操作结果
*/
@Transactional
public Object refund(String body) {
Integer orderId = JacksonUtil.parseInteger(body, "orderId");
String refundMoney = JacksonUtil.parseString(body, "refundMoney");
if (orderId == null) {
return ResponseUtil.badArgument();
}
if (StringUtils.isEmpty(refundMoney)) {
return ResponseUtil.badArgument();
}
LitemallOrder order = orderService.findById(orderId);
if (order == null) {
return ResponseUtil.badArgument();
}
if (order.getActualPrice().compareTo(new BigDecimal(refundMoney)) != 0) {
return ResponseUtil.badArgumentValue();
}
// 如果订单不是退款状态,则不能退款
if (!order.getOrderStatus().equals(OrderUtil.STATUS_REFUND)) {
return ResponseUtil.fail(ORDER_CONFIRM_NOT_ALLOWED, "订单不能确认收货");
}
// 微信退款
WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest();
wxPayRefundRequest.setOutTradeNo(order.getOrderSn());
wxPayRefundRequest.setOutRefundNo("refund_" + order.getOrderSn());
// 元转成分
Integer totalFee = order.getActualPrice().multiply(new BigDecimal(100)).intValue();
wxPayRefundRequest.setTotalFee(totalFee);
wxPayRefundRequest.setRefundFee(totalFee);
WxPayRefundResult wxPayRefundResult = null;
try {
wxPayRefundResult = wxPayService.refund(wxPayRefundRequest);
} catch (WxPayException e) {
e.printStackTrace();
return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败");
}
if (!wxPayRefundResult.getReturnCode().equals("SUCCESS")) {
logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg());
return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败");
}
if (!wxPayRefundResult.getResultCode().equals("SUCCESS")) {
logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg());
return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败");
}
// 设置订单取消状态
order.setOrderStatus(OrderUtil.STATUS_REFUND_CONFIRM);
if (orderService.updateWithOptimisticLocker(order) == 0) {
throw new RuntimeException("更新数据已失效");
}
// 商品货品数量增加
List<LitemallOrderGoods> orderGoodsList = orderGoodsService.queryByOid(orderId);
for (LitemallOrderGoods orderGoods : orderGoodsList) {
Integer productId = orderGoods.getProductId();
Short number = orderGoods.getNumber();
if (productService.addStock(productId, number) == 0) {
throw new RuntimeException("商品货品库存增加失败");
}
}
//TODO 发送邮件和短信通知,这里采用异步发送
// 退款成功通知用户, 例如“您申请的订单退款 [ 单号:{1} ] 已成功,请耐心等待到账。”
// 注意订单号只发后6位
notifyService.notifySmsTemplate(order.getMobile(), NotifyType.REFUND, new String[]{order.getOrderSn().substring(8, 14)});
logHelper.logOrderSucceed("退款", "订单编号 " + orderId);
return ResponseUtil.ok();
}
/**
* 发货
* 1. 检测当前订单是否能够发货
* 2. 设置订单发货状态
*
* @param body 订单信息,{ orderId:xxx, shipSn: xxx, shipChannel: xxx }
* @return 订单操作结果
* 成功则 { errno: 0, errmsg: '成功' }
* 失败则 { errno: XXX, errmsg: XXX }
*/
public Object ship(String body) {
Integer orderId = JacksonUtil.parseInteger(body, "orderId");
String shipSn = JacksonUtil.parseString(body, "shipSn");
String shipChannel = JacksonUtil.parseString(body, "shipChannel");
if (orderId == null || shipSn == null || shipChannel == null) {
return ResponseUtil.badArgument();
}
LitemallOrder order = orderService.findById(orderId);
if (order == null) {
return ResponseUtil.badArgument();
}
// 如果订单不是已付款状态,则不能发货
if (!order.getOrderStatus().equals(OrderUtil.STATUS_PAY)) {
return ResponseUtil.fail(ORDER_CONFIRM_NOT_ALLOWED, "订单不能确认收货");
}
order.setOrderStatus(OrderUtil.STATUS_SHIP);
order.setShipSn(shipSn);
order.setShipChannel(shipChannel);
order.setShipTime(LocalDateTime.now());
if (orderService.updateWithOptimisticLocker(order) == 0) {
return ResponseUtil.updatedDateExpired();
}
//TODO 发送邮件和短信通知,这里采用异步发送
// 发货会发送通知短信给用户: *
// "您的订单已经发货,快递公司 {1},快递单 {2} ,请注意查收"
notifyService.notifySmsTemplate(order.getMobile(), NotifyType.SHIP, new String[]{shipChannel, shipSn});
logHelper.logOrderSucceed("发货", "订单编号 " + orderId);
return ResponseUtil.ok();
}
/**
* 回复订单商品
*
* @param body 订单信息,{ orderId:xxx }
* @return 订单操作结果
* 成功则 { errno: 0, errmsg: '成功' }
* 失败则 { errno: XXX, errmsg: XXX }
*/
public Object reply(String body) {
Integer commentId = JacksonUtil.parseInteger(body, "commentId");
if (commentId == null || commentId == 0) {
return ResponseUtil.badArgument();
}
// 目前只支持回复一次
if (commentService.findById(commentId) != null) {
return ResponseUtil.fail(ORDER_REPLY_EXIST, "订单商品已回复!");
}
String content = JacksonUtil.parseString(body, "content");
if (StringUtils.isEmpty(content)) {
return ResponseUtil.badArgument();
}
// 创建评价回复
LitemallComment comment = new LitemallComment();
comment.setType((byte) 2);
comment.setValueId(commentId);
comment.setContent(content);
comment.setUserId(0); // 评价回复没有用
comment.setStar((short) 0); // 评价回复没有用
comment.setHasPicture(false); // 评价回复没有用
comment.setPicUrls(new String[]{}); // 评价回复没有用
commentService.save(comment);
return ResponseUtil.ok();
}
}
操作日志管理服务
/**
* 这里的日志类型设计成四种(当然开发者需要可以自己扩展)
* 一般日志:用户觉得需要查看的一般操作日志,建议是默认的日志级别
* 安全日志:用户安全相关的操作日志,例如登录、删除管理员
* 订单日志:用户交易相关的操作日志,例如订单发货、退款
* 其他日志:如果以上三种不合适,可以选择其他日志,建议是优先级最低的日志级别
*
* 当然可能很多操作是不需要记录到数据库的,例如编辑商品、编辑广告品之类。
*/
@Component
public class LogHelper {
public final static Integer LOG_TYPE_GENERAL = 0;
public final static Integer LOG_TYPE_AUTH = 1;
public final static Integer LOG_TYPE_ORDER = 2;
public final static Integer LOG_TYPE_OTHER = 3;
@Autowired
private LitemallLogService logService;
public void logGeneralSucceed(String action){
logAdmin(LOG_TYPE_GENERAL, action, true, "", "");
}
public void logGeneralSucceed(String action, String result){
logAdmin(LOG_TYPE_GENERAL, action, true, result, "");
}
public void logGeneralFail(String action, String error){
logAdmin(LOG_TYPE_GENERAL, action, false, error, "");
}
public void logAuthSucceed(String action){
logAdmin(LOG_TYPE_AUTH, action, true, "", "");
}
public void logAuthSucceed(String action, String result){
logAdmin(LOG_TYPE_AUTH, action, true, result, "");
}
public void logAuthFail(String action, String error){
logAdmin(LOG_TYPE_AUTH, action, false, error, "");
}
public void logOrderSucceed(String action){
logAdmin(LOG_TYPE_ORDER, action, true, "", "");
}
public void logOrderSucceed(String action, String result){
logAdmin(LOG_TYPE_ORDER, action, true, result, "");
}
public void logOrderFail(String action, String error){
logAdmin(LOG_TYPE_ORDER, action, false, error, "");
}
public void logOtherSucceed(String action){
logAdmin(LOG_TYPE_OTHER, action, true, "", "");
}
public void logOtherSucceed(String action, String result){
logAdmin(LOG_TYPE_OTHER, action, true, result, "");
}
public void logOtherFail(String action, String error){
logAdmin(LOG_TYPE_OTHER, action, false, error, "");
}
public void logAdmin (Integer type, String action, Boolean succeed, String result, String comment){
LitemallLog log = new LitemallLog();
Subject currentUser = SecurityUtils.getSubject();
if(currentUser != null) {
LitemallAdmin admin = (LitemallAdmin) currentUser.getPrincipal();
if(admin != null) {
log.setAdmin(admin.getUsername());
}
else{
log.setAdmin("匿名用户");
}
}
else{
log.setAdmin("匿名用户");
}
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if(request != null) {
log.setIp(IpUtil.getIpAddr(request));
}
log.setType(type);
log.setAction(action);
log.setStatus(succeed);
log.setResult(result);
log.setComment(comment);
logService.add(log);
}
}
顾客(会员)身份认证
public class AdminAuthorizingRealm extends AuthorizingRealm {
private static final Logger log = LoggerFactory.getLogger(AdminAuthorizingRealm.class);
@Autowired
private LitemallAdminService adminService;
@Autowired
private LitemallRoleService roleService;
@Autowired
private LitemallPermissionService permissionService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
LitemallAdmin admin = (LitemallAdmin) getAvailablePrincipal(principals);
Integer[] roleIds = admin.getRoleIds();
Set<String> roles = roleService.queryByIds(roleIds);
Set<String> permissions = permissionService.queryByRoleIds(roleIds);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles);
info.setStringPermissions(permissions);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
String password=new String(upToken.getPassword());
if (StringUtils.isEmpty(username)) {
throw new AccountException("用户名不能为空");
}
if (StringUtils.isEmpty(password)) {
throw new AccountException("密码不能为空");
}
List<LitemallAdmin> adminList = adminService.findAdmin(username);
Assert.state(adminList.size() < 2, "同一个用户名存在两个账户");
if (adminList.size() == 0) {
throw new UnknownAccountException("找不到用户("+username+")的帐号信息");
}
LitemallAdmin admin = adminList.get(0);
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
if (!encoder.matches(password, admin.getPassword())) {
throw new UnknownAccountException("找不到用户("+username+")的帐号信息");
}
return new SimpleAuthenticationInfo(admin,password,getName());
}
}
广告管理控制器
Ps:其他控制器与此类似,不在一一冗赘
@RestController
@RequestMapping("/admin/ad")
@Validated
public class AdminAdController {
private final Log logger = LogFactory.getLog(AdminAdController.class);
@Autowired
private LitemallAdService adService;
@RequiresPermissions("admin:ad:list")
@RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="查询")
@GetMapping("/list")
public Object list(String name, String content,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit,
@Sort @RequestParam(defaultValue = "add_time") String sort,
@Order @RequestParam(defaultValue = "desc") String order) {
List<LitemallAd> adList = adService.querySelective(name, content, page, limit, sort, order);
return ResponseUtil.okList(adList);
}
private Object validate(LitemallAd ad) {
String name = ad.getName();
if (StringUtils.isEmpty(name)) {
return ResponseUtil.badArgument();
}
String content = ad.getContent();
if (StringUtils.isEmpty(content)) {
return ResponseUtil.badArgument();
}
return null;
}
@RequiresPermissions("admin:ad:create")
@RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="添加")
@PostMapping("/create")
public Object create(@RequestBody LitemallAd ad) {
Object error = validate(ad);
if (error != null) {
return error;
}
adService.add(ad);
return ResponseUtil.ok(ad);
}
@RequiresPermissions("admin:ad:read")
@RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="详情")
@GetMapping("/read")
public Object read(@NotNull Integer id) {
LitemallAd ad = adService.findById(id);
return ResponseUtil.ok(ad);
}
@RequiresPermissions("admin:ad:update")
@RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="编辑")
@PostMapping("/update")
public Object update(@RequestBody LitemallAd ad) {
Object error = validate(ad);
if (error != null) {
return error;
}
if (adService.updateById(ad) == 0) {
return ResponseUtil.updatedDataFailed();
}
return ResponseUtil.ok(ad);
}
@RequiresPermissions("admin:ad:delete")
@RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="删除")
@PostMapping("/delete")
public Object delete(@RequestBody LitemallAd ad) {
Integer id = ad.getId();
if (id == null) {
return ResponseUtil.badArgument();
}
adService.deleteById(id);
return ResponseUtil.ok();
}
}
数据库操作部分
Ps:其他操作和对商品品牌数据库表操作类似,再次不在冗赘。
商品品牌数据库表操作
@Service
publicclassLitemallBrandService {
@Resource
privateLitemallBrandMapperbrandMapper;
privateColumn[] columns = newColumn[]{Column.id, Column.name, Column.desc, Column.picUrl, Column.floorPrice};
publicList<LitemallBrand> query(Integerpage, Integerlimit, Stringsort, Stringorder) {
LitemallBrandExampleexample = newLitemallBrandExample();
example.or().andDeletedEqualTo(false);
if (!StringUtils.isEmpty(sort) && !StringUtils.isEmpty(order)) {
example.setOrderByClause(sort + " " + order);
}
PageHelper.startPage(page, limit);
returnbrandMapper.selectByExampleSelective(example, columns);
}
publicList<LitemallBrand> query(Integerpage, Integerlimit) {
returnquery(page, limit, null, null);
}
publicLitemallBrandfindById(Integerid) {
returnbrandMapper.selectByPrimaryKey(id);
}
publicList<LitemallBrand> querySelective(Stringid, Stringname, Integerpage, Integersize, Stringsort, Stringorder) {
LitemallBrandExampleexample = newLitemallBrandExample();
LitemallBrandExample.Criteriacriteria = example.createCriteria();
if (!StringUtils.isEmpty(id)) {
criteria.andIdEqualTo(Integer.valueOf(id));
}
if (!StringUtils.isEmpty(name)) {
criteria.andNameLike("%" + name + "%");
}
criteria.andDeletedEqualTo(false);
if (!StringUtils.isEmpty(sort) && !StringUtils.isEmpty(order)) {
example.setOrderByClause(sort + " " + order);
}
PageHelper.startPage(page, size);
returnbrandMapper.selectByExample(example);
}
publicintupdateById(LitemallBrandbrand) {
brand.setUpdateTime(LocalDateTime.now());
returnbrandMapper.updateByPrimaryKeySelective(brand);
}
publicvoiddeleteById(Integerid) {
brandMapper.logicalDeleteByPrimaryKey(id);
}
publicvoidadd(LitemallBrandbrand) {
brand.setAddTime(LocalDateTime.now());
brand.setUpdateTime(LocalDateTime.now());
brandMapper.insertSelective(brand);
}
publicList<LitemallBrand> all() {
LitemallBrandExampleexample = newLitemallBrandExample();
example.or().andDeletedEqualTo(false);
returnbrandMapper.selectByExample(example);
}
}
核心操作部分
Ps:其他核心操作同存储(商品信息、图片对象等)操作、物流查询服务类似,不在冗赘。
存储操作
/**
* 对象存储接口
*/
publicinterfaceStorage {
/**
* 存储一个文件对象
*
* @paraminputStream 文件输入流
* @paramcontentLength文件长度
* @paramcontentType 文件类型
* @paramkeyName 文件名
*/
voidstore(InputStreaminputStream, longcontentLength, StringcontentType, StringkeyName);
Stream<Path> loadAll();
Pathload(StringkeyName);
ResourceloadAsResource(StringkeyName);
voiddelete(StringkeyName);
StringgenerateUrl(StringkeyName);
}
物流查询服务
/**
* 物流查询服务
*
* 快递鸟即时查询API http://www.kdniao.com/api-track
*/
public class ExpressService {
//请求url
private String ReqURL = "http://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx";
private ExpressProperties properties;
public ExpressProperties getProperties() {
return properties;
}
public void setProperties(ExpressProperties properties) {
this.properties = properties;
}
/**
* 获取物流供应商名
*
* @param vendorCode
* @return
*/
public String getVendorName(String vendorCode) {
for (Map<String, String> item : properties.getVendors()) {
if (item.get("code").equals(vendorCode))
return item.get("name");
}
return null;
}
/**
* 获取物流信息
*
* @param expCode
* @param expNo
* @return
*/
public ExpressInfo getExpressInfo(String expCode, String expNo) {
try {
String result = getOrderTracesByJson(expCode, expNo);
ObjectMapper objMap = new ObjectMapper();
ExpressInfo ei = objMap.readValue(result, ExpressInfo.class);
ei.setShipperName(getVendorName(expCode));
return ei;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Json方式 查询订单物流轨迹
*
* @throws Exception
*/
private String getOrderTracesByJson(String expCode, String expNo) throws Exception {
if (!properties.isEnable()) {
return null;
}
String requestData = "{'OrderCode':'','ShipperCode':'" + expCode + "','LogisticCode':'" + expNo + "'}";
Map<String, String> params = new HashMap<String, String>();
params.put("RequestData", URLEncoder.encode(requestData, "UTF-8"));
params.put("EBusinessID", properties.getAppId());
params.put("RequestType", "1002");
String dataSign = encrypt(requestData, properties.getAppKey(), "UTF-8");
params.put("DataSign", URLEncoder.encode(dataSign, "UTF-8"));
params.put("DataType", "2");
String result = HttpUtil.sendPost(ReqURL, params);
//根据公司业务处理返回的信息......
return result;
}
/**
* MD5加密
*
* @param str 内容
* @param charset 编码方式
* @throws Exception
*/
private String MD5(String str, String charset) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes(charset));
byte[] result = md.digest();
StringBuffer sb = new StringBuffer(32);
for (int i = 0; i < result.length; i++) {
int val = result[i] & 0xff;
if (val <= 0xf) {
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
return sb.toString().toLowerCase();
}
/**
* Sign签名生成
*
* @param content 内容
* @param keyValue Appkey
* @param charset 编码方式
* @return DataSign签名
*/
private String encrypt(String content, String keyValue, String charset) {
if (keyValue != null) {
content = content + keyValue;
}
byte[] src = new byte[0];
try {
src = MD5(content, charset).getBytes(charset);
return Base64Utils.encodeToString(src);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
迷你商城后端管理
应用程序主函数接口
@SpringBootApplication(scanBasePackages = {"org.linlinjava.litemall.db", "org.linlinjava.litemall.core", "org.linlinjava.litemall.admin"})
@MapperScan("org.linlinjava.litemall.db.dao")
@EnableTransactionManagement
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
优惠卷管理
/**
* 检测优惠券过期情况
*/
@Component
public class CouponJob {
private final Log logger = LogFactory.getLog(CouponJob.class);
@Autowired
private LitemallCouponService couponService;
@Autowired
private LitemallCouponUserService couponUserService;
/**
* 每隔一个小时检查
* TODO
* 注意,因为是相隔一个小时检查,因此导致优惠券真正超时时间可能比设定时间延迟1个小时
*/
@Scheduled(fixedDelay = 60 * 60 * 1000)
public void checkCouponExpired() {
logger.info("系统开启任务检查优惠券是否已经过期");
List<LitemallCoupon> couponList = couponService.queryExpired();
for(LitemallCoupon coupon : couponList){
coupon.setStatus(CouponConstant.STATUS_EXPIRED);
couponService.updateById(coupon);
}
List<LitemallCouponUser> couponUserList = couponUserService.queryExpired();
for(LitemallCouponUser couponUser : couponUserList){
couponUser.setStatus(CouponUserConstant.STATUS_EXPIRED);
couponUserService.update(couponUser);
}
}
}
订单管理
/**
* 检测订单状态
*/
@Component
public class OrderJob {
private final Log logger = LogFactory.getLog(OrderJob.class);
@Autowired
private LitemallOrderGoodsService orderGoodsService;
@Autowired
private LitemallOrderService orderService;
@Autowired
private LitemallGoodsProductService productService;
/**
* 自动取消订单
* <p>
* 定时检查订单未付款情况,如果超时 LITEMALL_ORDER_UNPAID 分钟则自动取消订单
* 定时时间是每次相隔半个小时。
* <p>
* TODO
* 注意,因为是相隔半小时检查,因此导致订单真正超时时间是 [LITEMALL_ORDER_UNPAID, 30 + LITEMALL_ORDER_UNPAID]
*/
@Scheduled(fixedDelay = 30 * 60 * 1000)
@Transactional
public void checkOrderUnpaid() {
logger.info("系统开启任务检查订单是否已经超期自动取消订单");
List<LitemallOrder> orderList = orderService.queryUnpaid(SystemConfig.getOrderUnpaid());
for (LitemallOrder order : orderList) {
// 设置订单已取消状态
order.setOrderStatus(OrderUtil.STATUS_AUTO_CANCEL);
order.setEndTime(LocalDateTime.now());
if (orderService.updateWithOptimisticLocker(order) == 0) {
throw new RuntimeException("更新数据已失效");
}
// 商品货品数量增加
Integer orderId = order.getId();
List<LitemallOrderGoods> orderGoodsList = orderGoodsService.queryByOid(orderId);
for (LitemallOrderGoods orderGoods : orderGoodsList) {
Integer productId = orderGoods.getProductId();
Short number = orderGoods.getNumber();
if (productService.addStock(productId, number) == 0) {
throw new RuntimeException("商品货品库存增加失败");
}
}
logger.info("订单 ID=" + order.getId() + " 已经超期自动取消订单");
}
}
/**
* 自动确认订单
* <p>
* 定时检查订单未确认情况,如果超时 LITEMALL_ORDER_UNCONFIRM 天则自动确认订单
* 定时时间是每天凌晨3点。
* <p>
* TODO
* 注意,因为是相隔一天检查,因此导致订单真正超时时间是 [LITEMALL_ORDER_UNCONFIRM, 1 + LITEMALL_ORDER_UNCONFIRM]
*/
@Scheduled(cron = "0 0 3 * * ?")
public void checkOrderUnconfirm() {
logger.info("系统开启任务检查订单是否已经超期自动确认收货");
List<LitemallOrder> orderList = orderService.queryUnconfirm(SystemConfig.getOrderUnconfirm());
for (LitemallOrder order : orderList) {
// 设置订单已取消状态
order.setOrderStatus(OrderUtil.STATUS_AUTO_CONFIRM);
order.setConfirmTime(LocalDateTime.now());
if (orderService.updateWithOptimisticLocker(order) == 0) {
logger.info("订单 ID=" + order.getId() + " 数据已经更新,放弃自动确认收货");
} else {
logger.info("订单 ID=" + order.getId() + " 已经超期自动确认收货");
}
}
}
/**
* 可评价订单商品超期
* <p>
* 定时检查订单商品评价情况,如果确认商品超时 LITEMALL_ORDER_COMMENT 天则取消可评价状态
* 定时时间是每天凌晨4点。
* <p>
* TODO
* 注意,因为是相隔一天检查,因此导致订单真正超时时间是 [LITEMALL_ORDER_COMMENT, 1 + LITEMALL_ORDER_COMMENT]
*/
@Scheduled(cron = "0 0 4 * * ?")
public void checkOrderComment() {
logger.info("系统开启任务检查订单是否已经超期未评价");
LocalDateTime now = LocalDateTime.now();
List<LitemallOrder> orderList = orderService.queryComment(SystemConfig.getOrderComment());
for (LitemallOrder order : orderList) {
order.setComments((short) 0);
orderService.updateWithOptimisticLocker(order);
List<LitemallOrderGoods> orderGoodsList = orderGoodsService.queryByOid(order.getId());
for (LitemallOrderGoods orderGoods : orderGoodsList) {
orderGoods.setComment(-1);
orderGoodsService.updateById(orderGoods);
}
}
}
}
上架商品管理服务
@Service
public class AdminGoodsService {
private final Log logger = LogFactory.getLog(AdminGoodsService.class);
@Autowired
private LitemallGoodsService goodsService;
@Autowired
private LitemallGoodsSpecificationService specificationService;
@Autowired
private LitemallGoodsAttributeService attributeService;
@Autowired
private LitemallGoodsProductService productService;
@Autowired
private LitemallCategoryService categoryService;
@Autowired
private LitemallBrandService brandService;
@Autowired
private LitemallCartService cartService;
@Autowired
private LitemallOrderGoodsService orderGoodsService;
@Autowired
private QCodeService qCodeService;
public Object list(String goodsSn, String name,
Integer page, Integer limit, String sort, String order) {
List<LitemallGoods> goodsList = goodsService.querySelective(goodsSn, name, page, limit, sort, order);
return ResponseUtil.okList(goodsList);
}
private Object validate(GoodsAllinone goodsAllinone) {
LitemallGoods goods = goodsAllinone.getGoods();
String name = goods.getName();
if (StringUtils.isEmpty(name)) {
return ResponseUtil.badArgument();
}
String goodsSn = goods.getGoodsSn();
if (StringUtils.isEmpty(goodsSn)) {
return ResponseUtil.badArgument();
}
// 品牌商可以不设置,如果设置则需要验证品牌商存在
Integer brandId = goods.getBrandId();
if (brandId != null && brandId != 0) {
if (brandService.findById(brandId) == null) {
return ResponseUtil.badArgumentValue();
}
}
// 分类可以不设置,如果设置则需要验证分类存在
Integer categoryId = goods.getCategoryId();
if (categoryId != null && categoryId != 0) {
if (categoryService.findById(categoryId) == null) {
return ResponseUtil.badArgumentValue();
}
}
LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes();
for (LitemallGoodsAttribute attribute : attributes) {
String attr = attribute.getAttribute();
if (StringUtils.isEmpty(attr)) {
return ResponseUtil.badArgument();
}
String value = attribute.getValue();
if (StringUtils.isEmpty(value)) {
return ResponseUtil.badArgument();
}
}
LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications();
for (LitemallGoodsSpecification specification : specifications) {
String spec = specification.getSpecification();
if (StringUtils.isEmpty(spec)) {
return ResponseUtil.badArgument();
}
String value = specification.getValue();
if (StringUtils.isEmpty(value)) {
return ResponseUtil.badArgument();
}
}
LitemallGoodsProduct[] products = goodsAllinone.getProducts();
for (LitemallGoodsProduct product : products) {
Integer number = product.getNumber();
if (number == null || number < 0) {
return ResponseUtil.badArgument();
}
BigDecimal price = product.getPrice();
if (price == null) {
return ResponseUtil.badArgument();
}
String[] productSpecifications = product.getSpecifications();
if (productSpecifications.length == 0) {
return ResponseUtil.badArgument();
}
}
return null;
}
/**
* 编辑商品
* <p>
* TODO
* 目前商品修改的逻辑是
* 1. 更新litemall_goods表
* 2. 逻辑删除litemall_goods_specification、litemall_goods_attribute、litemall_goods_product
* 3. 添加litemall_goods_specification、litemall_goods_attribute、litemall_goods_product
* <p>
* 这里商品三个表的数据采用删除再添加的策略是因为
* 商品编辑页面,支持管理员添加删除商品规格、添加删除商品属性,因此这里仅仅更新是不可能的,
* 只能删除三个表旧的数据,然后添加新的数据。
* 但是这里又会引入新的问题,就是存在订单商品货品ID指向了失效的商品货品表。
* 因此这里会拒绝管理员编辑商品,如果订单或购物车中存在商品。
* 所以这里可能需要重新设计。
*/
@Transactional
public Object update(GoodsAllinone goodsAllinone) {
Object error = validate(goodsAllinone);
if (error != null) {
return error;
}
LitemallGoods goods = goodsAllinone.getGoods();
LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes();
LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications();
LitemallGoodsProduct[] products = goodsAllinone.getProducts();
Integer id = goods.getId();
//将生成的分享图片地址写入数据库
String url = qCodeService.createGoodShareImage(goods.getId().toString(), goods.getPicUrl(), goods.getName());
goods.setShareUrl(url);
// 商品基本信息表litemall_goods
if (goodsService.updateById(goods) == 0) {
throw new RuntimeException("更新数据失败");
}
Integer gid = goods.getId();
specificationService.deleteByGid(gid);
attributeService.deleteByGid(gid);
productService.deleteByGid(gid);
// 商品规格表litemall_goods_specification
for (LitemallGoodsSpecification specification : specifications) {
specification.setGoodsId(goods.getId());
specificationService.add(specification);
}
// 商品参数表litemall_goods_attribute
for (LitemallGoodsAttribute attribute : attributes) {
attribute.setGoodsId(goods.getId());
attributeService.add(attribute);
}
// 商品货品表litemall_product
for (LitemallGoodsProduct product : products) {
product.setGoodsId(goods.getId());
productService.add(product);
}
return ResponseUtil.ok();
}
@Transactional
public Object delete(LitemallGoods goods) {
Integer id = goods.getId();
if (id == null) {
return ResponseUtil.badArgument();
}
Integer gid = goods.getId();
goodsService.deleteById(gid);
specificationService.deleteByGid(gid);
attributeService.deleteByGid(gid);
productService.deleteByGid(gid);
return ResponseUtil.ok();
}
@Transactional
public Object create(GoodsAllinone goodsAllinone) {
Object error = validate(goodsAllinone);
if (error != null) {
return error;
}
LitemallGoods goods = goodsAllinone.getGoods();
LitemallGoodsAttribute[] attributes = goodsAllinone.getAttributes();
LitemallGoodsSpecification[] specifications = goodsAllinone.getSpecifications();
LitemallGoodsProduct[] products = goodsAllinone.getProducts();
String name = goods.getName();
if (goodsService.checkExistByName(name)) {
return ResponseUtil.fail(GOODS_NAME_EXIST, "商品名已经存在");
}
// 商品基本信息表litemall_goods
goodsService.add(goods);
//将生成的分享图片地址写入数据库
String url = qCodeService.createGoodShareImage(goods.getId().toString(), goods.getPicUrl(), goods.getName());
if (!StringUtils.isEmpty(url)) {
goods.setShareUrl(url);
if (goodsService.updateById(goods) == 0) {
throw new RuntimeException("更新数据失败");
}
}
// 商品规格表litemall_goods_specification
for (LitemallGoodsSpecification specification : specifications) {
specification.setGoodsId(goods.getId());
specificationService.add(specification);
}
// 商品参数表litemall_goods_attribute
for (LitemallGoodsAttribute attribute : attributes) {
attribute.setGoodsId(goods.getId());
attributeService.add(attribute);
}
// 商品货品表litemall_product
for (LitemallGoodsProduct product : products) {
product.setGoodsId(goods.getId());
productService.add(product);
}
return ResponseUtil.ok();
}
public Object list2() {
// http://element-cn.eleme.io/#/zh-CN/component/cascader
// 管理员设置“所属分类”
List<LitemallCategory> l1CatList = categoryService.queryL1();
List<CatVo> categoryList = new ArrayList<>(l1CatList.size());
for (LitemallCategory l1 : l1CatList) {
CatVo l1CatVo = new CatVo();
l1CatVo.setValue(l1.getId());
l1CatVo.setLabel(l1.getName());
List<LitemallCategory> l2CatList = categoryService.queryByPid(l1.getId());
List<CatVo> children = new ArrayList<>(l2CatList.size());
for (LitemallCategory l2 : l2CatList) {
CatVo l2CatVo = new CatVo();
l2CatVo.setValue(l2.getId());
l2CatVo.setLabel(l2.getName());
children.add(l2CatVo);
}
l1CatVo.setChildren(children);
categoryList.add(l1CatVo);
}
// http://element-cn.eleme.io/#/zh-CN/component/select
// 管理员设置“所属品牌商”
List<LitemallBrand> list = brandService.all();
List<Map<String, Object>> brandList = new ArrayList<>(l1CatList.size());
for (LitemallBrand brand : list) {
Map<String, Object> b = new HashMap<>(2);
b.put("value", brand.getId());
b.put("label", brand.getName());
brandList.add(b);
}
Map<String, Object> data = new HashMap<>();
data.put("categoryList", categoryList);
data.put("brandList", brandList);
return ResponseUtil.ok(data);
}
public Object detail(Integer id) {
LitemallGoods goods = goodsService.findById(id);
List<LitemallGoodsProduct> products = productService.queryByGid(id);
List<LitemallGoodsSpecification> specifications = specificationService.queryByGid(id);
List<LitemallGoodsAttribute> attributes = attributeService.queryByGid(id);
Integer categoryId = goods.getCategoryId();
LitemallCategory category = categoryService.findById(categoryId);
Integer[] categoryIds = new Integer[]{};
if (category != null) {
Integer parentCategoryId = category.getPid();
categoryIds = new Integer[]{parentCategoryId, categoryId};
}
Map<String, Object> data = new HashMap<>();
data.put("goods", goods);
data.put("specifications", specifications);
data.put("products", products);
data.put("attributes", attributes);
data.put("categoryIds", categoryIds);
return ResponseUtil.ok(data);
}
}
订单管理服务
@Service
public class AdminOrderService {
private final Log logger = LogFactory.getLog(AdminOrderService.class);
@Autowired
private LitemallOrderGoodsService orderGoodsService;
@Autowired
private LitemallOrderService orderService;
@Autowired
private LitemallGoodsProductService productService;
@Autowired
private LitemallUserService userService;
@Autowired
private LitemallCommentService commentService;
@Autowired
private WxPayService wxPayService;
@Autowired
private NotifyService notifyService;
@Autowired
private LogHelper logHelper;
public Object list(Integer userId, String orderSn, List<Short> orderStatusArray,
Integer page, Integer limit, String sort, String order) {
List<LitemallOrder> orderList = orderService.querySelective(userId, orderSn, orderStatusArray, page, limit, sort, order);
return ResponseUtil.okList(orderList);
}
public Object detail(Integer id) {
LitemallOrder order = orderService.findById(id);
List<LitemallOrderGoods> orderGoods = orderGoodsService.queryByOid(id);
UserVo user = userService.findUserVoById(order.getUserId());
Map<String, Object> data = new HashMap<>();
data.put("order", order);
data.put("orderGoods", orderGoods);
data.put("user", user);
return ResponseUtil.ok(data);
}
/**
* 订单退款
* <p>
* 1. 检测当前订单是否能够退款;
* 2. 微信退款操作;
* 3. 设置订单退款确认状态;
* 4. 订单商品库存回库。
* <p>
* TODO
* 虽然接入了微信退款API,但是从安全角度考虑,建议开发者删除这里微信退款代码,采用以下两步走步骤:
* 1. 管理员登录微信官方支付平台点击退款操作进行退款
* 2. 管理员登录litemall管理后台点击退款操作进行订单状态修改和商品库存回库
*
* @param body 订单信息,{ orderId:xxx }
* @return 订单退款操作结果
*/
@Transactional
public Object refund(String body) {
Integer orderId = JacksonUtil.parseInteger(body, "orderId");
String refundMoney = JacksonUtil.parseString(body, "refundMoney");
if (orderId == null) {
return ResponseUtil.badArgument();
}
if (StringUtils.isEmpty(refundMoney)) {
return ResponseUtil.badArgument();
}
LitemallOrder order = orderService.findById(orderId);
if (order == null) {
return ResponseUtil.badArgument();
}
if (order.getActualPrice().compareTo(new BigDecimal(refundMoney)) != 0) {
return ResponseUtil.badArgumentValue();
}
// 如果订单不是退款状态,则不能退款
if (!order.getOrderStatus().equals(OrderUtil.STATUS_REFUND)) {
return ResponseUtil.fail(ORDER_CONFIRM_NOT_ALLOWED, "订单不能确认收货");
}
// 微信退款
WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest();
wxPayRefundRequest.setOutTradeNo(order.getOrderSn());
wxPayRefundRequest.setOutRefundNo("refund_" + order.getOrderSn());
// 元转成分
Integer totalFee = order.getActualPrice().multiply(new BigDecimal(100)).intValue();
wxPayRefundRequest.setTotalFee(totalFee);
wxPayRefundRequest.setRefundFee(totalFee);
WxPayRefundResult wxPayRefundResult = null;
try {
wxPayRefundResult = wxPayService.refund(wxPayRefundRequest);
} catch (WxPayException e) {
e.printStackTrace();
return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败");
}
if (!wxPayRefundResult.getReturnCode().equals("SUCCESS")) {
logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg());
return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败");
}
if (!wxPayRefundResult.getResultCode().equals("SUCCESS")) {
logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg());
return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败");
}
// 设置订单取消状态
order.setOrderStatus(OrderUtil.STATUS_REFUND_CONFIRM);
if (orderService.updateWithOptimisticLocker(order) == 0) {
throw new RuntimeException("更新数据已失效");
}
// 商品货品数量增加
List<LitemallOrderGoods> orderGoodsList = orderGoodsService.queryByOid(orderId);
for (LitemallOrderGoods orderGoods : orderGoodsList) {
Integer productId = orderGoods.getProductId();
Short number = orderGoods.getNumber();
if (productService.addStock(productId, number) == 0) {
throw new RuntimeException("商品货品库存增加失败");
}
}
//TODO 发送邮件和短信通知,这里采用异步发送
// 退款成功通知用户, 例如“您申请的订单退款 [ 单号:{1} ] 已成功,请耐心等待到账。”
// 注意订单号只发后6位
notifyService.notifySmsTemplate(order.getMobile(), NotifyType.REFUND, new String[]{order.getOrderSn().substring(8, 14)});
logHelper.logOrderSucceed("退款", "订单编号 " + orderId);
return ResponseUtil.ok();
}
/**
* 发货
* 1. 检测当前订单是否能够发货
* 2. 设置订单发货状态
*
* @param body 订单信息,{ orderId:xxx, shipSn: xxx, shipChannel: xxx }
* @return 订单操作结果
* 成功则 { errno: 0, errmsg: '成功' }
* 失败则 { errno: XXX, errmsg: XXX }
*/
public Object ship(String body) {
Integer orderId = JacksonUtil.parseInteger(body, "orderId");
String shipSn = JacksonUtil.parseString(body, "shipSn");
String shipChannel = JacksonUtil.parseString(body, "shipChannel");
if (orderId == null || shipSn == null || shipChannel == null) {
return ResponseUtil.badArgument();
}
LitemallOrder order = orderService.findById(orderId);
if (order == null) {
return ResponseUtil.badArgument();
}
// 如果订单不是已付款状态,则不能发货
if (!order.getOrderStatus().equals(OrderUtil.STATUS_PAY)) {
return ResponseUtil.fail(ORDER_CONFIRM_NOT_ALLOWED, "订单不能确认收货");
}
order.setOrderStatus(OrderUtil.STATUS_SHIP);
order.setShipSn(shipSn);
order.setShipChannel(shipChannel);
order.setShipTime(LocalDateTime.now());
if (orderService.updateWithOptimisticLocker(order) == 0) {
return ResponseUtil.updatedDateExpired();
}
//TODO 发送邮件和短信通知,这里采用异步发送
// 发货会发送通知短信给用户: *
// "您的订单已经发货,快递公司 {1},快递单 {2} ,请注意查收"
notifyService.notifySmsTemplate(order.getMobile(), NotifyType.SHIP, new String[]{shipChannel, shipSn});
logHelper.logOrderSucceed("发货", "订单编号 " + orderId);
return ResponseUtil.ok();
}
/**
* 回复订单商品
*
* @param body 订单信息,{ orderId:xxx }
* @return 订单操作结果
* 成功则 { errno: 0, errmsg: '成功' }
* 失败则 { errno: XXX, errmsg: XXX }
*/
public Object reply(String body) {
Integer commentId = JacksonUtil.parseInteger(body, "commentId");
if (commentId == null || commentId == 0) {
return ResponseUtil.badArgument();
}
// 目前只支持回复一次
if (commentService.findById(commentId) != null) {
return ResponseUtil.fail(ORDER_REPLY_EXIST, "订单商品已回复!");
}
String content = JacksonUtil.parseString(body, "content");
if (StringUtils.isEmpty(content)) {
return ResponseUtil.badArgument();
}
// 创建评价回复
LitemallComment comment = new LitemallComment();
comment.setType((byte) 2);
comment.setValueId(commentId);
comment.setContent(content);
comment.setUserId(0); // 评价回复没有用
comment.setStar((short) 0); // 评价回复没有用
comment.setHasPicture(false); // 评价回复没有用
comment.setPicUrls(new String[]{}); // 评价回复没有用
commentService.save(comment);
return ResponseUtil.ok();
}
}
操作日志管理服务
/**
* 这里的日志类型设计成四种(当然开发者需要可以自己扩展)
* 一般日志:用户觉得需要查看的一般操作日志,建议是默认的日志级别
* 安全日志:用户安全相关的操作日志,例如登录、删除管理员
* 订单日志:用户交易相关的操作日志,例如订单发货、退款
* 其他日志:如果以上三种不合适,可以选择其他日志,建议是优先级最低的日志级别
*
* 当然可能很多操作是不需要记录到数据库的,例如编辑商品、编辑广告品之类。
*/
@Component
public class LogHelper {
public final static Integer LOG_TYPE_GENERAL = 0;
public final static Integer LOG_TYPE_AUTH = 1;
public final static Integer LOG_TYPE_ORDER = 2;
public final static Integer LOG_TYPE_OTHER = 3;
@Autowired
private LitemallLogService logService;
public void logGeneralSucceed(String action){
logAdmin(LOG_TYPE_GENERAL, action, true, "", "");
}
public void logGeneralSucceed(String action, String result){
logAdmin(LOG_TYPE_GENERAL, action, true, result, "");
}
public void logGeneralFail(String action, String error){
logAdmin(LOG_TYPE_GENERAL, action, false, error, "");
}
public void logAuthSucceed(String action){
logAdmin(LOG_TYPE_AUTH, action, true, "", "");
}
public void logAuthSucceed(String action, String result){
logAdmin(LOG_TYPE_AUTH, action, true, result, "");
}
public void logAuthFail(String action, String error){
logAdmin(LOG_TYPE_AUTH, action, false, error, "");
}
public void logOrderSucceed(String action){
logAdmin(LOG_TYPE_ORDER, action, true, "", "");
}
public void logOrderSucceed(String action, String result){
logAdmin(LOG_TYPE_ORDER, action, true, result, "");
}
public void logOrderFail(String action, String error){
logAdmin(LOG_TYPE_ORDER, action, false, error, "");
}
public void logOtherSucceed(String action){
logAdmin(LOG_TYPE_OTHER, action, true, "", "");
}
public void logOtherSucceed(String action, String result){
logAdmin(LOG_TYPE_OTHER, action, true, result, "");
}
public void logOtherFail(String action, String error){
logAdmin(LOG_TYPE_OTHER, action, false, error, "");
}
public void logAdmin (Integer type, String action, Boolean succeed, String result, String comment){
LitemallLog log = new LitemallLog();
Subject currentUser = SecurityUtils.getSubject();
if(currentUser != null) {
LitemallAdmin admin = (LitemallAdmin) currentUser.getPrincipal();
if(admin != null) {
log.setAdmin(admin.getUsername());
}
else{
log.setAdmin("匿名用户");
}
}
else{
log.setAdmin("匿名用户");
}
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if(request != null) {
log.setIp(IpUtil.getIpAddr(request));
}
log.setType(type);
log.setAction(action);
log.setStatus(succeed);
log.setResult(result);
log.setComment(comment);
logService.add(log);
}
}
顾客(会员)身份认证
public class AdminAuthorizingRealm extends AuthorizingRealm {
private static final Logger log = LoggerFactory.getLogger(AdminAuthorizingRealm.class);
@Autowired
private LitemallAdminService adminService;
@Autowired
private LitemallRoleService roleService;
@Autowired
private LitemallPermissionService permissionService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
LitemallAdmin admin = (LitemallAdmin) getAvailablePrincipal(principals);
Integer[] roleIds = admin.getRoleIds();
Set<String> roles = roleService.queryByIds(roleIds);
Set<String> permissions = permissionService.queryByRoleIds(roleIds);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles);
info.setStringPermissions(permissions);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
String password=new String(upToken.getPassword());
if (StringUtils.isEmpty(username)) {
throw new AccountException("用户名不能为空");
}
if (StringUtils.isEmpty(password)) {
throw new AccountException("密码不能为空");
}
List<LitemallAdmin> adminList = adminService.findAdmin(username);
Assert.state(adminList.size() < 2, "同一个用户名存在两个账户");
if (adminList.size() == 0) {
throw new UnknownAccountException("找不到用户("+username+")的帐号信息");
}
LitemallAdmin admin = adminList.get(0);
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
if (!encoder.matches(password, admin.getPassword())) {
throw new UnknownAccountException("找不到用户("+username+")的帐号信息");
}
return new SimpleAuthenticationInfo(admin,password,getName());
}
}
广告管理控制器
Ps:其他控制器与此类似,不在一一冗赘
@RestController
@RequestMapping("/admin/ad")
@Validated
public class AdminAdController {
private final Log logger = LogFactory.getLog(AdminAdController.class);
@Autowired
private LitemallAdService adService;
@RequiresPermissions("admin:ad:list")
@RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="查询")
@GetMapping("/list")
public Object list(String name, String content,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit,
@Sort @RequestParam(defaultValue = "add_time") String sort,
@Order @RequestParam(defaultValue = "desc") String order) {
List<LitemallAd> adList = adService.querySelective(name, content, page, limit, sort, order);
return ResponseUtil.okList(adList);
}
private Object validate(LitemallAd ad) {
String name = ad.getName();
if (StringUtils.isEmpty(name)) {
return ResponseUtil.badArgument();
}
String content = ad.getContent();
if (StringUtils.isEmpty(content)) {
return ResponseUtil.badArgument();
}
return null;
}
@RequiresPermissions("admin:ad:create")
@RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="添加")
@PostMapping("/create")
public Object create(@RequestBody LitemallAd ad) {
Object error = validate(ad);
if (error != null) {
return error;
}
adService.add(ad);
return ResponseUtil.ok(ad);
}
@RequiresPermissions("admin:ad:read")
@RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="详情")
@GetMapping("/read")
public Object read(@NotNull Integer id) {
LitemallAd ad = adService.findById(id);
return ResponseUtil.ok(ad);
}
@RequiresPermissions("admin:ad:update")
@RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="编辑")
@PostMapping("/update")
public Object update(@RequestBody LitemallAd ad) {
Object error = validate(ad);
if (error != null) {
return error;
}
if (adService.updateById(ad) == 0) {
return ResponseUtil.updatedDataFailed();
}
return ResponseUtil.ok(ad);
}
@RequiresPermissions("admin:ad:delete")
@RequiresPermissionsDesc(menu={"推广管理" , "广告管理"}, button="删除")
@PostMapping("/delete")
public Object delete(@RequestBody LitemallAd ad) {
Integer id = ad.getId();
if (id == null) {
return ResponseUtil.badArgument();
}
adService.deleteById(id);
return ResponseUtil.ok();
}
}
数据库操作部分
Ps:其他操作和对商品品牌数据库表操作类似,再次不在冗赘。
商品品牌数据库表操作
@Service
publicclassLitemallBrandService {
@Resource
privateLitemallBrandMapperbrandMapper;
privateColumn[] columns = newColumn[]{Column.id, Column.name, Column.desc, Column.picUrl, Column.floorPrice};
publicList<LitemallBrand> query(Integerpage, Integerlimit, Stringsort, Stringorder) {
LitemallBrandExampleexample = newLitemallBrandExample();
example.or().andDeletedEqualTo(false);
if (!StringUtils.isEmpty(sort) && !StringUtils.isEmpty(order)) {
example.setOrderByClause(sort + " " + order);
}
PageHelper.startPage(page, limit);
returnbrandMapper.selectByExampleSelective(example, columns);
}
publicList<LitemallBrand> query(Integerpage, Integerlimit) {
returnquery(page, limit, null, null);
}
publicLitemallBrandfindById(Integerid) {
returnbrandMapper.selectByPrimaryKey(id);
}
publicList<LitemallBrand> querySelective(Stringid, Stringname, Integerpage, Integersize, Stringsort, Stringorder) {
LitemallBrandExampleexample = newLitemallBrandExample();
LitemallBrandExample.Criteriacriteria = example.createCriteria();
if (!StringUtils.isEmpty(id)) {
criteria.andIdEqualTo(Integer.valueOf(id));
}
if (!StringUtils.isEmpty(name)) {
criteria.andNameLike("%" + name + "%");
}
criteria.andDeletedEqualTo(false);
if (!StringUtils.isEmpty(sort) && !StringUtils.isEmpty(order)) {
example.setOrderByClause(sort + " " + order);
}
PageHelper.startPage(page, size);
returnbrandMapper.selectByExample(example);
}
publicintupdateById(LitemallBrandbrand) {
brand.setUpdateTime(LocalDateTime.now());
returnbrandMapper.updateByPrimaryKeySelective(brand);
}
publicvoiddeleteById(Integerid) {
brandMapper.logicalDeleteByPrimaryKey(id);
}
publicvoidadd(LitemallBrandbrand) {
brand.setAddTime(LocalDateTime.now());
brand.setUpdateTime(LocalDateTime.now());
brandMapper.insertSelective(brand);
}
publicList<LitemallBrand> all() {
LitemallBrandExampleexample = newLitemallBrandExample();
example.or().andDeletedEqualTo(false);
returnbrandMapper.selectByExample(example);
}
}
核心操作部分
Ps:其他核心操作同存储(商品信息、图片对象等)操作、物流查询服务类似,不在冗赘。
存储操作
/**
* 对象存储接口
*/
publicinterfaceStorage {
/**
* 存储一个文件对象
*
* @paraminputStream 文件输入流
* @paramcontentLength文件长度
* @paramcontentType 文件类型
* @paramkeyName 文件名
*/
voidstore(InputStreaminputStream, longcontentLength, StringcontentType, StringkeyName);
Stream<Path> loadAll();
Pathload(StringkeyName);
ResourceloadAsResource(StringkeyName);
voiddelete(StringkeyName);
StringgenerateUrl(StringkeyName);
}
物流查询服务
/**
* 物流查询服务
*
* 快递鸟即时查询API http://www.kdniao.com/api-track
*/
public class ExpressService {
//请求url
private String ReqURL = "http://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx";
private ExpressProperties properties;
public ExpressProperties getProperties() {
return properties;
}
public void setProperties(ExpressProperties properties) {
this.properties = properties;
}
/**
* 获取物流供应商名
*
* @param vendorCode
* @return
*/
public String getVendorName(String vendorCode) {
for (Map<String, String> item : properties.getVendors()) {
if (item.get("code").equals(vendorCode))
return item.get("name");
}
return null;
}
/**
* 获取物流信息
*
* @param expCode
* @param expNo
* @return
*/
public ExpressInfo getExpressInfo(String expCode, String expNo) {
try {
String result = getOrderTracesByJson(expCode, expNo);
ObjectMapper objMap = new ObjectMapper();
ExpressInfo ei = objMap.readValue(result, ExpressInfo.class);
ei.setShipperName(getVendorName(expCode));
return ei;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Json方式 查询订单物流轨迹
*
* @throws Exception
*/
private String getOrderTracesByJson(String expCode, String expNo) throws Exception {
if (!properties.isEnable()) {
return null;
}
String requestData = "{'OrderCode':'','ShipperCode':'" + expCode + "','LogisticCode':'" + expNo + "'}";
Map<String, String> params = new HashMap<String, String>();
params.put("RequestData", URLEncoder.encode(requestData, "UTF-8"));
params.put("EBusinessID", properties.getAppId());
params.put("RequestType", "1002");
String dataSign = encrypt(requestData, properties.getAppKey(), "UTF-8");
params.put("DataSign", URLEncoder.encode(dataSign, "UTF-8"));
params.put("DataType", "2");
String result = HttpUtil.sendPost(ReqURL, params);
//根据公司业务处理返回的信息......
return result;
}
/**
* MD5加密
*
* @param str 内容
* @param charset 编码方式
* @throws Exception
*/
private String MD5(String str, String charset) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes(charset));
byte[] result = md.digest();
StringBuffer sb = new StringBuffer(32);
for (int i = 0; i < result.length; i++) {
int val = result[i] & 0xff;
if (val <= 0xf) {
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
return sb.toString().toLowerCase();
}
/**
* Sign签名生成
*
* @param content 内容
* @param keyValue Appkey
* @param charset 编码方式
* @return DataSign签名
*/
private String encrypt(String content, String keyValue, String charset) {
if (keyValue != null) {
content = content + keyValue;
}
byte[] src = new byte[0];
try {
src = MD5(content, charset).getBytes(charset);
return Base64Utils.encodeToString(src);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}