宠物乐园

宠物乐园是我最近做的一个O2O项目,支持商家入驻,为线下门店提供客源,从中抽取分成,实现互利共赢。
随着生活水平的提高,人们对精神方面的需求也不断提高,很多人喜欢通过喂养宠物来丰富自己的精神需求,于是公司就研发了宠物乐园这个项目,这个项目主要功能:用户登录此平台可以领养宠物,平台的宠物来源主要分为两种,一是基地饲养,二是用户没精力喂养宠物时可以通过该平台发布宠物相关信息,平台就近分配给附近的线下门店,门店安排员工上门进行回收。回收后进行打理并训练后上架。
用户可以在线上为自己的宠物购买服务(上门洗澡,美容,寄养等),用户下单后就近分配给附近的线下门店。
项目后端使用springboot_mybatis使用的数据库是mysql和redis
项目分为7个模块组织机构管理模块,用户模块,服务模块,宠物模块,订单模块,支付模块,定时任务模块,其中我做了宠物模块、订单模块及支付模块
宠物模块的主要包括寻主和领养:
寻主:用户登录平台后点击发布填写自己不想继续喂养的宠物信息进行发布,平台收到信息就近分配给入驻商家, 商家员工上门进行回收,回收资金由员工垫付后报账,收回宠物后处理寻主信息将寻主信息状态修改为已处理,并将宠物信息完善后加入宠物表中。
商家可以对自己的宠物进行上下架。
用户在主站上领养已经上架的宠物,点击领养后生成宠物订单,订单地址和支付订单并调用支付方法进行支付,并设置30分钟取消的定时任务
其中使用了百度地图api(输入地址时自动联想)、fastdfs技术(上传图片信息)、docker(部署fastdfs)、支付宝等三方支付api(支付),中国网建api等(发送消息)。

//文件上传工具类封装
package org.chengang.basic.util;

import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;

public class FastDfsUtil {

    //从classpath
    public static String CONF_FILENAME  = FastDfsUtil.class.getClassLoader()
            .getResource("fdfs_client.conf").getFile();


    /**
     * 上传文件
     * @param file
     * @param extName
     * @return
     */
    public static  String upload(byte[] file,String extName) {

        try {
            ClientGlobal.init(CONF_FILENAME);

            TrackerClient tracker = new TrackerClient();
            TrackerServer trackerServer = tracker.getConnection();
            StorageServer storageServer = null;

            StorageClient storageClient = new StorageClient(trackerServer, storageServer);
            NameValuePair nvp [] = new NameValuePair[]{
                    new NameValuePair("age", "18"),
                    new NameValuePair("sex", "male")
            };
            String fileIds[] = storageClient.upload_file(file,extName,nvp);

            System.out.println(fileIds.length);
            System.out.println("组名:" + fileIds[0]);
            System.out.println("路径: " + fileIds[1]);
            return  "/"+fileIds[0]+"/"+fileIds[1];

        } catch (Exception e) {
            e.printStackTrace();
            return  null;
        }
    }
    /**
     * 上传文件
     * @param  path 上传文件路径
     * @param extName 后缀名
     * @return /group1/M00/00/00/rBEABV9QmHGACKp8AAWYzsKwDu4441.bmp
     */
    public static  String upload(String path,String extName) {
 
        try { 
            ClientGlobal.init(CONF_FILENAME);
 
            TrackerClient tracker = new TrackerClient(); 
            TrackerServer trackerServer = tracker.getConnection(); 
            StorageServer storageServer = null;
            StorageClient storageClient = new StorageClient(trackerServer, storageServer);
            String fileIds[] = storageClient.upload_file(path, extName,null);
             
            System.out.println(fileIds.length); 
            System.out.println("组名:" + fileIds[0]); 
            System.out.println("路径: " + fileIds[1]);
            return  "/"+fileIds[0]+"/"+fileIds[1];
 
        } catch (Exception e) {
            e.printStackTrace();
            return  null;
        }
    }

    /**
     * 下载文件
     * @param groupName
     * @param fileName
     * @return
     */
    public static byte[] download(String groupName,String fileName) {
        try {
 
            ClientGlobal.init(CONF_FILENAME);
 
            TrackerClient tracker = new TrackerClient(); 
            TrackerServer trackerServer = tracker.getConnection(); 
            StorageServer storageServer = null;
 
            StorageClient storageClient = new StorageClient(trackerServer, storageServer); 
            byte[] b = storageClient.download_file(groupName, fileName);
            return  b;
        } catch (Exception e) {
            e.printStackTrace();
            return  null;
        } 
    }


    /**
     * 删除文件
     * @param groupName
     * @param fileName
     */
    public static void delete(String groupName,String fileName){
        try { 
            ClientGlobal.init(CONF_FILENAME);
 
            TrackerClient tracker = new TrackerClient(); 
            TrackerServer trackerServer = tracker.getConnection(); 
            StorageServer storageServer = null;
 
            StorageClient storageClient = new StorageClient(trackerServer, 
                    storageServer); 
            int i = storageClient.delete_file(groupName,fileName);
            System.out.println( i==0 ? "删除成功" : "删除失败:"+i);
        } catch (Exception e) {
            e.printStackTrace();
            throw  new RuntimeException("删除异常,"+e.getMessage());
        } 
    }


}
//fastdfs配置文件
fdfs_client.conf
   tracker_server=122.51.119.246:22122
package org.chengang.basic.util;

import java.io.UnsupportedEncodingException;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.chengang.basic.constant.MsgConstant;

public class SendMsg_webchinese {
    private static  PostMethod post=null;
    public static String sendMsg(String phone,String content) {

        try {
            HttpClient client = new HttpClient();
            post = new PostMethod("http://utf8.api.smschinese.cn/");//Utf-8编码Url 中国网建官网有
            post.addRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf8");//在头文件中设置转码
            NameValuePair[] data = {new NameValuePair("Uid", MsgConstant.UID), new NameValuePair("Key", MsgConstant.KEY), new NameValuePair("smsMob", phone), new NameValuePair("smsText", content)};
            post.setRequestBody(data);
            client.executeMethod(post);
            Header[] headers = post.getResponseHeaders();
            int statusCode = post.getStatusCode();
            System.out.println("statusCode:" + statusCode);
            for (Header h : headers) {
                System.out.println(h.toString());
            }
            String result = new String(post.getResponseBodyAsString().getBytes("utf8"));
            System.out.println(result); //打印返回消息状态
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {
            post.releaseConnection();

        }

    }

}
//使用百度地图api 封装工具类 通过字符串地址获取经纬度
//根据两点间经纬度坐标(double值),计算两点间距离,单位为米
package org.chengang.basic.util;


import org.chengang.basic.domain.Point;
import org.chengang.org.domain.Shop;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;



public class DistanceUtil {

  /**
   * @param address
   */
//通过字符串地址获取经纬度
  public static  Point getPoint(String address){
      String Application_ID="msGZMt2RFGa0V9InVgYytSmivbQGLafs";//配置上自己的百度地图应用的AK
//      String Application_ID="bZdIzawfzYoZ2cukPLM0CgOb2PK8rgtH";//配置上自己的百度地图应用的AK
      try{
          String sCurrentLine; String sTotalString;sCurrentLine ="";
          sTotalString = "";
          InputStream l_urlStream;
//          URL l_url = new java.net.URL("http://api.map.baidu.com/geocoder/v2/?address="+address.replaceAll(" ","")+"&output=json&ak="+Application_ID+"&callback=showLocation");
          URL l_url = new URL("http://api.map.baidu.com/geocoding/v3/?address="+address+"&output=json&ak="+Application_ID+"&callback=showLocation");
          HttpURLConnection l_connection = (HttpURLConnection) l_url.openConnection();
      l_connection.connect();
      l_urlStream = l_connection.getInputStream();
      java.io.BufferedReader l_reader = new java.io.BufferedReader(new InputStreamReader(l_urlStream));
      String str=l_reader.readLine();
          System.out.println(str);
          //用经度分割返回的网页代码  
      String s=","+"\""+"lat"+"\""+":";
      String strs[]=str.split(s,2);
      String s1="\""+"lng"+"\""+":";
      String a[]=strs[0].split(s1, 2);
      s1="}"+","+"\"";
      String a1[]=strs[1].split(s1,2);

      Point point=new Point();
      point.setLng(Double.valueOf(a[1]));
      point.setLat(Double.valueOf(a1[0]));
      return point;
  } catch (Exception e) {
e.printStackTrace();
      return null;
    }
  }

    public static void main(String[] args) {
        System.out.println(getPoint("成都"));
    }

    //地球半径,进行经纬度运算需要用到的数据之一
    private static final double EARTH_RADIUS = 6378137;
    //根据坐标点获取弧度
    private static double rad(double d)
    {
        return d * Math.PI / 180.0;
    }

    /**
     * 根据两点间经纬度坐标(double值),计算两点间距离,单位为米
     * @param point1 A点坐标
     * @param point2 B点坐标
     * @return
     */
    public static double getDistance(Point point1,Point point2)
    {
        double radLat1 = rad(point1.getLat());
        double radLat2 = rad(point2.getLat());
        double a = radLat1 - radLat2;
        double b = rad(point1.getLng()) - rad(point2.getLng());
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a/2),2) +
                Math.cos(radLat1)*Math.cos(radLat2)*Math.pow(Math.sin(b/2),2)));
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000) / 10000;
        return s;
    }

    /**
     * 根据两点间经纬度坐标(double值),计算两点间距离,单位为米
     * @param point 用户指定的地址坐标
     * @param shops 商店
     * @return
     */
    public static Shop getNearestShop (Point point, List<Shop> shops) {

        //如果传过来的集合只有一家店铺,那么直接将这家店铺的信息返回就是最近的店铺了
        Shop shop=shops.get(0);
        //获取集合中第一家店铺到指定地点的距离
        double distance=getDistance(point,getPoint(shops.get(0).getAddress()));
        //如果有多家店铺,那么就和第一家店铺到指定地点的距离做比较
        if (shops.size()>1){
            for (int i=1;i<shops.size();i++){
                if (getDistance(point,getPoint(shops.get(i).getAddress()))<distance){
                    shop=shops.get(i);
                }
            }
        }
        return shop;
    }

}
支付宝支付工具类封装
package org.chengang.pay.utils;

import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import org.chengang.pay.constants.AlipayConfig;
import org.chengang.pay.domain.AlipayInfo;
import org.chengang.pay.domain.PayBill;

public class AlipayUtils {
    /**
     *
     *
     * @param info AlipayInfo 店铺的支付宝信息
     * @param payBill 支付单信息
     * @return
     */
    public static String pay(AlipayInfo info, PayBill payBill){
        try {
            //获得初始化的AlipayClient
            AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, info.getAppid(), info.getMerchant_private_key(),
                    "json", AlipayConfig.charset, info.getAlipay_public_key(), AlipayConfig.sign_type);

            //设置请求参数
            AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
            alipayRequest.setReturnUrl(AlipayConfig.return_url);
            alipayRequest.setNotifyUrl(AlipayConfig.notify_url);

            //商户订单号,商户网站订单系统中唯一订单号,必填
            String out_trade_no = new String(payBill.getUnionPaySn());
            //付款金额,必填
            String total_amount = new String(payBill.getMoney().toString());
            //订单名称,必填
            String subject = new String(payBill.getDigest());
            //商品描述,可空
            String body = new String("服务订单!!!!!");

            alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
                    + "\"total_amount\":\""+ total_amount +"\","
                    + "\"subject\":\""+ subject +"\","
                    + "\"body\":\""+ body +"\","
                    + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");


            //请求
            String result = alipayClient.pageExecute(alipayRequest).getBody();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;

    }
}
支付宝支付配置文件
package org.chengang.pay.constants;

/* *
 *类名:AlipayConfig
 *功能:基础配置类
 *详细:设置帐户有关信息及返回路径
 *修改日期:2017-04-05
 *说明:
 *以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
 *该代码仅供学习和研究支付宝接口使用,只是提供一个参考。
 */



public class AlipayConfig {

//↓↓↓↓↓↓↓↓↓↓请在这里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓


    // 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
    public static String alipay_public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIgHnOn7LLILlKETd6BFRJ0GqgS2Y3mn1wMQmyh9zEyWlz5p1zrahRahbXAfCfSqshSNfqOmAQzSHRVjCqjsAw1jyqrXaPdKBmr90DIpIxmIyKXv4GGAkPyJ/6FTFY99uhpiq0qadD/uSzQsefWo0aTvP/65zi3eof7TcZ32oWpwIDAQAB";

    // 服务器异步通知页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    public static String notify_url = "http://ust4fs.natappfree.cc/notify";

    // 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    //同步通知 本地请求
    public static String return_url = "http://localhost/success.html";
    // 支付宝网关
    public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";
    // 签名方式
    public static String sign_type = "RSA2";

    // 字符编码格式
    public static String charset = "utf-8";

}
支付宝支付工具类封装
package org.chengang.pay.utils;

import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import org.chengang.pay.constants.AlipayConfig;
import org.chengang.pay.domain.AlipayInfo;
import org.chengang.pay.domain.PayBill;

public class AlipayUtils {
    /**
     *
     *
     * @param info AlipayInfo 店铺的支付宝信息
     * @param payBill 支付单信息
     * @return
     */
    public static String pay(AlipayInfo info, PayBill payBill){
        try {
            //获得初始化的AlipayClient
            AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, info.getAppid(), info.getMerchant_private_key(),
                    "json", AlipayConfig.charset, info.getAlipay_public_key(), AlipayConfig.sign_type);

            //设置请求参数
            AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
            alipayRequest.setReturnUrl(AlipayConfig.return_url);
            alipayRequest.setNotifyUrl(AlipayConfig.notify_url);

            //商户订单号,商户网站订单系统中唯一订单号,必填
            String out_trade_no = new String(payBill.getUnionPaySn());
            //付款金额,必填
            String total_amount = new String(payBill.getMoney().toString());
            //订单名称,必填
            String subject = new String(payBill.getDigest());
            //商品描述,可空
            String body = new String("服务订单!!!!!");

            alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
                    + "\"total_amount\":\""+ total_amount +"\","
                    + "\"subject\":\""+ subject +"\","
                    + "\"body\":\""+ body +"\","
                    + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");


            //请求
            String result = alipayClient.pageExecute(alipayRequest).getBody();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;

    }
}
支付宝回调接口
package org.chengang.pay.controller;


import com.alipay.api.internal.util.AlipaySignature;
import org.chengang.order.domain.ProductOrder;
import org.chengang.order.service.IProductOrderService;
import org.chengang.pay.constants.AlipayConfig;
import org.chengang.pay.constants.PayConstant;
import org.chengang.pay.domain.AlipayInfo;
import org.chengang.pay.domain.PayBill;
import org.chengang.pay.service.IAlipayInfoService;
import org.chengang.pay.service.IPayBillService;
import org.chengang.quartz.JobContstants;
import org.chengang.quartz.service.IQuartzService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

@RestController
public class AlipayController {

    @Autowired
    private IPayBillService payBillService;

    @Autowired
    private IAlipayInfoService alipayInfoService;

    @Autowired
    private IProductOrderService productOrderService;
    @Autowired
    private IQuartzService quartzService;

    //回调方法
    @PostMapping("/notify")
    public String notifyMethod(HttpServletRequest request){
        try {
            //获取支付宝POST过来反馈信息
            Map<String,String> params = new HashMap<String,String>();
            Map<String,String[]> requestParams = request.getParameterMap();
            for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
                String name = (String) iter.next();
                String[] values = (String[]) requestParams.get(name);
                String valueStr = "";
                for (int i = 0; i < values.length; i++) {
                    valueStr = (i == values.length - 1) ? valueStr + values[i]
                            : valueStr + values[i] + ",";
                }
                //乱码解决,这段代码在出现乱码时使用
                valueStr = new String(valueStr);
                params.put(name, valueStr);
            }

//        System.out.println(params);
//        for (String key : params.keySet()) {
//            System.out.println(key);
//            System.out.println(params.get(key));
//        }
            //out_trade_no-->unionPaySn
            String unionPaySn = params.get("out_trade_no");
            PayBill payBill = payBillService.queryPayBillByUnionPaySn(unionPaySn);
            Long shopId = payBill.getShop_id();
            AlipayInfo info = alipayInfoService.getByShopId(shopId);
            boolean signVerified = AlipaySignature.rsaCheckV1(params, info.getAlipay_public_key(), AlipayConfig.charset, AlipayConfig.sign_type); //调用SDK验证签名

            //——请在这里编写您的程序(以下代码仅作参考)——

	/* 实际验证过程建议商户务必添加以下校验:
	1、需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
	2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
	3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
	4、验证app_id是否为该商户本身。
	*/
            if(signVerified) {//验证成功
                //商户订单号
                String out_trade_no = request.getParameter("out_trade_no");

                //支付宝交易号
                String trade_no = request.getParameter("trade_no");

                //交易状态
                String trade_status = request.getParameter("trade_status");

                if(trade_status.equals("TRADE_FINISHED")){
                    //判断该笔订单是否在商户网站中已经做过处理
                    //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
                    //如果有做过处理,不执行商户的业务程序
                    //注意:
                    //退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
                }else if (trade_status.equals("TRADE_SUCCESS")){ //支付成功

                    //修改支付单状态
                    payBill.setState(1);
                    payBillService.update(payBill);
                    //修改订单状态
                    String businessType = payBill.getBusinessType();
                    Long businessKey = payBill.getBusinessKey();
                    if(PayConstant.BUSINESS_TYPE_PRODUCT_ORDER.equals(businessType)){
                        //服务订单
                        ProductOrder productOrder = productOrderService.getById(businessKey);
                        productOrder.setState(1);
                        productOrderService.update(productOrder);
                        quartzService.deleteJob("product_order_cancel_"+productOrder.getId());
                        return "success";
                        //支付成功删除定时任务

                    }
                }
            }else {//验证失败

                //调试用,写文本函数记录程序运行情况是否正常
                //String sWord = AlipaySignature.getSignCheckContentV1(params);
                //AlipayConfig.logResult(sWord);
            }

            //——请在这里编写您的程序(以上代码仅作参考)——

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }



}

业务接口略