@SpringBootApplication(scanBasePackages = {"org.linlinjava.litemall.db", "org.linlinjava.litemall.core", "org.linlinjava.litemall.admin"})java
@MapperScan("org.linlinjava.litemall.db.dao")數據庫
@EnableTransactionManagement後端
@EnableSchedulingapi
public class Application {安全
public static void main(String[] args) {微信
SpringApplication.run(Application.class, args);app
}異步
}ide
/**函數
* 檢測優惠券過時狀況
*/
@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;
}
}