微信小程序

开发设置

服务器域名:必须是https,不能是IP,域名必须已备案,可以配置多个

登录

1、前端调用wx.login() 得到 jsCode,传给后端
2、后端验证jsCode,得到openid、session_key(用于解密前端发来的加密数据)

    @Autowired
    private RestTemplate restTemplate;

        String appId = "***";
        String appSecret = "***";
        if (jsCode == null) {
            throw new RestfulException("登录失败");
        }
       
        String url = String.format("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", appId, appSecret, jsCode);
        WxSession wxSession = restTemplate.getForObject(url, WxSession.class);
        if (wxSession.getErrcode() != 0) {
            throw new RestfulException("验证失败");
        } else {
             // 验证成功,将微信返回的内容保存的session
             request.getSession().setAttribute("wxSession", wxSession);
        }
@JsonInclude(JsonInclude.Include.NON_NULL)
public class WxSession implements Serializable {
    private String jsCode;       // 前端登录请求里的字段
    private String openid;       // 登录微信服务器返回的字段
    private String session_key;  // 登录微信服务器返回的字段
    private int errcode;
    private String errmsg;
    @NotBlank
    private String iv;             // 前端发来的加密算法的初始向量
    @NotBlank
    private String encryptedData;   // 前端发来的加密数据(phoneNumber、unionId)
}

获取手机号

0、前提 已经 wx.login(),且 后端存有 session_key
1、在前端设置 open-type="getPhoneNumber" 的按钮,得到 iv 和 encryptedData
2、后端解密 encryptedData

    @RequestMapping(value = "/customer/mobile", method = RequestMethod.PUT)
    public RestfulResult setMobile(@Validated @RequestBody WxSession data, @SessionAttribute("wxSession") WxSession wxSession) throws Exception {
        // 密钥
        byte[] sessionKey = Base64.decodeBase64(wxSession.getSession_key());
        // 格式化密钥
        SecretKeySpec keySpec = new SecretKeySpec(sessionKey, "AES");
        // 初始化向量
        byte[] iv =Base64.decodeBase64(data.getIv());
        // 格式化 初始化向量
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        // 确定算法
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // 进入解密模式
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        // 密文
        byte[] encryptedData = Base64.decodeBase64(data.getEncryptedData());
        // 解密
        String plainData = new String(cipher.doFinal(encryptedData),"UTF-8");
        // FastJson 库 ,JSON字符串转对象
        JSONObject jsonObject = JSON.parseObject(plainData);
        String mobile = jsonObject.getString("phoneNumber");

        return new RestfulResult(mobile);
    }

获取 unionId

前提:微信开放平台上绑定了小程序
方式一:在登录时(code2Session)时得到 unionid,此时无法预知unionid是否存在,因此不建议使用此方式
方式二:在登录后,判断后端是否已存有unionId,没有的话,前端调用wx.getUserInfo({withCredentials: true})得到加密数据,发给后端解密

    @ApiOperation(value = "解析小程序getUserInfo得到的敏感数据")
    @RequestMapping(value = "/encrypted-data", method = RequestMethod.PUT)
    public RestfulResult setUnionId(@Validated @RequestBody WxSession data, @SessionAttribute WxSession wxSession, @SessionAttribute Customer userInfo) throws Exception {
        String signature = DigestUtils.sha1Hex(data.getRawData() + wxSession.getSession_key());
        if(!data.getSignature().equals(signature)){    // 验证前端发来的signature是否正确
            throw new RestfulException("签名不正确");
        }
        /* 解密过程参考上方的获取手机号 */
        String unionId = jsonObject.getString("unionId");
        if(unionId != null){
            customerService.setUnionId(userInfo.getId(), unionId);
        }
        return new RestfulResult(unionId);
    }

服务消息推送

一、模板消息(已于2020年1月10日下线)
0、消息模板在微信公众平台上管理
1、使用form组件,用户提交表单时,得到 formId,后端保存 formId,可在 7 天内向用户推送 1 次模板消息
2、用户完成支付,得到 prepay_id,后端保存 prepay_id,可在 7 天内向用户推送 3 次模板消息
3、服务器端调用的接口为 /cgi-bin/message/wxopen/template/uniform_send

二、订阅消息
0、消息模板在微信公众平台上管理
1、前端调用 wx.requestSubscribeMessage,弹出授权框,可以一次授权多个消息模板
2、此订阅为一次性订阅,用户授权 1 次,同一个消息模板只能推送 1 条消息,时间不限;用户多次授权,可以累加
3、服务器端调用的接口为 /cgi-bin/message/subscribe/send
4、开通长期订阅的途径,在 微信开发社区 联系 社区技术运营专员 申请,原则是仅对线下公共服务开放

模板数据 里的一项数据

public class TemplateDataItem implements Serializable {
    private String value;
    public TemplateDataItem(String value) {
        this.value = value;
    }
}

模板数据,具体字段 在 微信公众平台上查看

public class TemplateData implements Serializable {
    private TemplateDataItem thing1;
    private TemplateDataItem phrase1;
    private TemplateDataItem amount1;
    private TemplateDataItem time1; 
}

推送请求

public class SubscribeRequest implements Serializable {
    private String access_token;
    private String touser;               // 目标用户的openId
    private String template_id = "***";  // 模板ID 在 微信公众平台上查看
    private String page = "pages/home/index";  // 点击服务通知卡片 进入到的页面路径
    private TemplateData data;

    public SubscribeRequest(String access_token, String touser) {
        this.access_token = access_token;
        this.touser = touser;
    }
}

推送

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private WeixinService weixinService;  // 参考 https://www.jianshu.com/p/9ceb8103f1b2


    private String subscribeUrl = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=%s";
    private String openId = "xx";

    public void sendMessage() {
        String accessToken = weixinService.getAccessToken();
        String urlStr = String.format(subscribeUrl, accessToken);
        SubscribeRequest request = new SubscribeRequest(accessToken, openId);
        TemplateData data = new TemplateData();

         data.setPhrase1(new TemplateDataItem("得是5个内汉字")); 
         data.setTime1(new TemplateDataItem("得是年月日"));
         data.setThing1(new TemplateDataItem("***"));
         data.setAmount1(new TemplateDataItem("****"));

         request.setData(data);
         WxSession wxSession = restTemplate.postForObject(urlStr, request, WxSession.class);
    }

微信支付

一、微信登录,参考上方
二、向微信服务器下单
1、Service

    // user 即用户对象,order即订单对象
    public Map place(User user, Order order) throws DocumentException {
        orderDao.place(user.getId(), order);  // 将订单存到数据库
        WxPay wxPay = new WxPay(notifyUrl, user.getOpenId(), order.getOrderId(), "商品名",  order.getSum());
        Map responseDataMap = this.unifiedOrder(wxPay);   // 向微信服务器下单
        Map dataForFront = this.dataForFront((String) responseDataMap.get("prepay_id"));  // 生成给前端的数据
        return dataForFront;
    }

    /* 统一下单 */
    public Map unifiedOrder(WxPay wxPay) throws DocumentException {
        Map<String, String> dataMap = new HashMap<>();
        dataMap.put("appid", appId);  // 小程序appId
        dataMap.put("body", wxPay.getBody());   // 商品描述
        dataMap.put("mch_id", mchId);    // 商户id
        dataMap.put("nonce_str", RandomStringUtils.randomAlphanumeric(16));  // 随机串
        dataMap.put("notify_url", wxPay.getNotify_url());   // 回调地址
        dataMap.put("out_trade_no", wxPay.getOut_trade_no());  // 订单号
        dataMap.put("openid", wxPay.getOpenid());   // trade_type=JSAPI时,openid必填
        dataMap.put("sign_type", "MD5");   // 签名算法
        dataMap.put("spbill_create_ip", billCreateIp);  // 我方服务器的公网IP
        dataMap.put("total_fee", wxPay.getTotal_fee());  // 金额
        dataMap.put("trade_type", "JSAPI");
        String sign = this.sign(dataMap);    // 签名
        dataMap.put("sign", sign);

        Map<String, Map> xmlMap = new HashMap<>();
        xmlMap.put("xml", dataMap);    // 注意需要最外层的<xml>标签
        StringBuffer sb = new StringBuffer();
        String xml = XMLUtil.parseMap(xmlMap, sb);   // Map 转 xml字符串,参考https://www.jianshu.com/p/fc5001b5b5bc

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_XML);
        HttpEntity<String> requestEntity = new HttpEntity<>(xml, headers);  // 构造请求数据
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(placeOrderUrl, requestEntity, String.class); // 发送请求

        Map responseXmlMap = XMLUtil.parseXml(responseEntity.getBody());  // 将xml转换成 map
        Map responseDataMap = (Map) responseXmlMap.get("xml");  // 注意有最外层的<xml>标签
        if ("FAIL".equals(responseDataMap.get("return_code"))) {
            throw new RestfulException((String) responseDataMap.get("return_msg"));
        }
        if ("FAIL".equals(responseDataMap.get("result_code"))) {
            throw new RestfulException((String) responseDataMap.get("err_code_des"));
        }
        return responseDataMap;
    }

    /* 签名 */
    public String sign(Map map) {
        List<String> list = new ArrayList(map.keySet());
        Collections.sort(list);  // 字典排序
        StringBuffer sb = new StringBuffer();
        for (String key : list) {
            sb.append(key + "=" + map.get(key) + "&");  // 拼接参数
        }
        sb.append("key=" + mchKey);      // 拼上商户号的API key,注意不是小程序的app secret
        return DigestUtils.md5Hex(sb.toString()).toUpperCase();  // MD5 摘要,并转大写
    }

    /* 给前端调用wx.requestPayment()的数据 及其 签名 */
    public Map dataForFront(String prepay_id) {
        Map<String, String> map = new HashMap<>();
        map.put("appId", appId);
        map.put("nonceStr", RandomStringUtils.randomAlphanumeric(16));
        map.put("package", "prepay_id=" + prepay_id);
        map.put("timeStamp", "" + (System.currentTimeMillis() / 1000));
        map.put("signType", "MD5");
        String sign = this.sign(map);
        map.put("paySign", sign);
        return map;
    }

2、pojo

public class WxPay implements Serializable {
    private String body;  // 商品描述
    private String notify_url;   // 统一下单的回调地址
    private String out_trade_no;  // 我方的订单ID
    private String openid;
    private String total_fee;    // 费用,以分为单位

    public WxPay(String notify_url, String openid, String out_trade_no, String body, String total_fee) {
        this.body = body;
        this.notify_url = notify_url;
        this.out_trade_no = out_trade_no;
        this.openid = openid;
        this.total_fee = total_fee;
    }
}

三、前端调用 wx.requestPayment()
四、处理支付通知
1、Service

    public String payNotify(String requestXml) throws DocumentException {
        String responseXml = "<xml><return_code>SUCCESS</return_code></xml>";

        Map requestDataMap = this.preTreat(requestXml);
        long orderId = Long.parseLong((String) requestDataMap.get("out_trade_no"));
        Order order = orderDao.getOrderByOrderId(orderId);
        this.notifyValidate(requestDataMap, order.getSum());
        if ("FAIL".equals(requestDataMap.get("result_code"))) {
            orderDao.setOrderStatus(order.getId(), "fail");   // 更新数据库里的订单状态
            return responseXml; 
        }
        orderDao.setOrderStatus(order.getId(), "success");   // 更新数据库里的订单状态
        return responseXml;
    }

    /* 预处理 支付通知 */
    public Map preTreat(String requestXml) throws DocumentException {
        Map requestXmlMap = XMLUtil.parseXml(requestXml);
        Map requestDataMap = (Map) requestXmlMap.get("xml");
        if ("FAIL".equals(requestDataMap.get("return_code"))) {
            throw new RestfulException("通信错误");
        }
        return requestDataMap;
    }

    /* 校验 支付通知 */
    public void notifyValidate(Map notifyMap,int sum){
        String wxSign = (String) notifyMap.get("sign");
        notifyMap.remove("sign");
        String mySign = this.sign(notifyMap);
        if (!wxSign.equals(mySign)) {                        // 校验签名
            throw new RestfulException("签名不正确");
        }
        if (sum != Integer.parseInt((String) notifyMap.get("total_fee"))) {      // 校验金额
            throw new RestfulException("金额不正确");
        }
    }

微信退款

一、申请退款
1、从商户平台上下载证书 .p12
2、给RestTemplate设置证书

    @Bean("RestTemplateWithCert")
    // 传入 证书存放的路径 和 商户ID
    public RestTemplate restTemplate(@Value("${wx.p12Path}") String keyFilePath, @Value("${wx.mchId}") String keyPassword) {
        HttpComponentsClientHttpRequestFactory httpRequestFactory;
        try {
            InputStream keyStream = new FileInputStream(new File(keyFilePath));  // 从绝对路径取
            InputStream keyStream = this.getClass().getResourceAsStream(keyFilePath);  // 从 resources 目录里取
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(keyStream, keyPassword.toCharArray());
            SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, keyPassword.toCharArray()).build();
            CloseableHttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
//            SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, null, NoopHostnameVerifier.INSTANCE);
//            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build();
            httpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException | CertificateException | IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
        RestTemplate restTemplate = new RestTemplate(httpRequestFactory);
        restTemplate.getMessageConverters().add(new FastJsonHttpMessageConverter());
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        return restTemplate;
    }

3、申请退款 Service

    public void agreeRefund(int orderId) throws DocumentException {
        Order order = orderDao.getOrderById(orderId);
        WxPay wxPay = new WxPay(refundNotifyUrl,"" + order.getTradeNum(), "" + order.getSum());
        this.refund(wxPay);
        orderDao.setOrderStatus(orderId, "refunding");   // 在数据库中更新订单状态
    }
    public Map refund(WxPay wxPay) throws DocumentException {
        Map<String, String> dataMap = new HashMap<>();
        dataMap.put("appid", appId);
        dataMap.put("mch_id", mchId);
        dataMap.put("nonce_str", RandomStringUtils.randomAlphanumeric(16));
        dataMap.put("notify_url", wxPay.getNotify_url());
        dataMap.put("sign_type", "MD5");
        dataMap.put("out_trade_no", wxPay.getOut_trade_no());  // 订单号
        dataMap.put("out_refund_no", wxPay.getOut_trade_no());  // 退款订单号
        dataMap.put("total_fee", wxPay.getTotal_fee());  // 订单金额
        dataMap.put("refund_fee", wxPay.getTotal_fee());  // 退款金额
        String sign = this.sign(dataMap);    // 签名
        dataMap.put("sign", sign);

        Map<String, Map> xmlMap = new HashMap<>();
        xmlMap.put("xml", dataMap);    // 注意需要最外层的<xml>标签
        StringBuffer sb = new StringBuffer();
        String xml = XMLUtil.parseMap(xmlMap, sb);   // Map 转 xml字符串

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_XML);
        HttpEntity<String> requestEntity = new HttpEntity<>(xml, headers);
        ResponseEntity<String> responseEntity = restTemplateWithCert.postForEntity(refundUrl, requestEntity, String.class); // 使用带证书的 RestTemplate

        Map responseXmlMap = XMLUtil.parseXml(responseEntity.getBody());
        Map responseDataMap = (Map) responseXmlMap.get("xml");  // 注意有最外层的<xml>标签
        if ("FAIL".equals(responseDataMap.get("return_code"))) {
            throw new RestfulException((String) responseDataMap.get("return_msg"));
        }
        if ("FAIL".equals(responseDataMap.get("result_code"))) {
            throw new RestfulException((String) responseDataMap.get("err_code_des"));
        }
        return responseDataMap;
    }

二、退款结果通知
1、pom.xml

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk16</artifactId>
            <version>1.46</version>
        </dependency>

2、AES/ECB/PKCS7Padding 解密算法工具

    @Bean("PKCS7Cipher")
    public Cipher cipher(@Value("${wx.mchKey}") String mchKey) throws Exception {
        Security.addProvider(new BouncyCastleProvider());  // BouncyCastleProvider 来自 org.bouncycastle.bcprov-jdk16
        // 对商户的API key取MD5摘要,注意不是商户ID
        String keyString = DigestUtils.md5Hex(mchKey);
        Key key = new SecretKeySpec(keyString.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher;
    }

3、退款通知处理

    @Transactional
    public String refundNotify(String requestXml) throws Exception {
        Map requestDataMap = this.preTreat(requestXml);
        String reqInfo = (String) requestDataMap.get("req_info");
        String reqInfoXml = this.decrypt(reqInfo);
        Map rootMap = XMLUtil.parseXml(reqInfoXml);
        Map dataMap = (Map)rootMap.get("root");   // 注意最外面有一层<root>
        long tradeNum = Long.parseLong((String)dataMap.get("out_trade_no"));
        String refundStatus = (String)dataMap.get("refund_status");
        if ("SUCCESS".equals(refundStatus)) {
            orderDao.setOrderStatusByTradeNum(tradeNum, ServeOrderStatus.REFUNDED.name().toLowerCase());
        }
        else if ("REFUNDCLOSE".equals(dataMap.get("refund_status")) || "CHANGE".equals(dataMap.get("refund_status"))) {
            orderDao.setOrderStatusByTradeNum(tradeNum, ServeOrderStatus.REFUND_FAIL.name().toLowerCase());
        }
        return "<xml><return_code>SUCCESS</return_code></xml>";
    }

    /* 退款通知解密 */
    public String decrypt(String reqInfo) throws BadPaddingException, IllegalBlockSizeException {
        // Base64解码得到字节数组后不要转为字符串,因为解密的输入是就是字节数组。字节数组转字符串,再转回字节数组,可能会带来变化
        byte[] cipherBytes = Base64.decodeBase64(reqInfo);
        byte[] plainBytes = pkcs7Cipher.doFinal(cipherBytes);
        String plainText = new String(plainBytes);
        return plainText;
    }

获取小程序码

    public String getUnlimited() {
        String accessToken = weixinService.fetchToken();
        // 注意,request里不能带access_token
        AppCodeRequest request = new AppCodeRequest();
        request.setPage("pages/activity/index");
        request.setScene(String.format("id=**", 1));   // 注意,不能大于32个字符
        String urlStr = String.format("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s", accessToken);
        byte[] bytes = restTemplate.postForObject(urlStr, request,  byte[].class);
        String base64 = new String(Base64.encodeBase64(bytes));
        base64 = base64.replaceAll("=", "");  // 去掉可能会有的等号,多等号可能对前端写入到本地有影响
        return base64;  // 返回Base64;要写到canvas里的话,不能直接用base64,需要存到本地得到path
    }

pojo

public class AppCodeRequest implements Serializable {
    private String scene;
    private String page;
}

企业付款

从商户号付款到用户余额,商户号要达到一定条件 才能开通

    public Map mmpay(WxPay wxPay) throws DocumentException {
        Map<String, String> dataMap = new HashMap<>();
        dataMap.put("mch_appid", appId);
        dataMap.put("mchid", mchId);
        dataMap.put("nonce_str", RandomStringUtils.randomAlphanumeric(16));
        dataMap.put("partner_trade_no", wxPay.getOut_trade_no());  // 订单号
        dataMap.put("openid", wxPay.getOpenid());
        dataMap.put("check_name", "NO_CHECK");  // 不校验实名
        dataMap.put("amount", wxPay.getTotal_fee());  // 付款金额
        dataMap.put("desc", wxPay.getBody());   // 付款备注

        String sign = this.sign(dataMap);    // 签名
        dataMap.put("sign", sign);

        String xml = this.generateXml(dataMap);   // 生成请求xml
        ResponseEntity<String> responseEntity = this.postXmlWithCert(mmpayUrl, xml);  // 发送xml
        Map responseDataMap = this.preTreatMmpay(responseEntity.getBody());  // 处理响应xml
        return responseDataMap;
    }

    public ResponseEntity postXmlWithCert(String url, String xml) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_XML);
        HttpEntity<String> requestEntity = new HttpEntity<>(xml, headers);
        ResponseEntity<String> responseEntity = restTemplateWithCert.postForEntity(url, requestEntity, String.class);
        return responseEntity;
    }

    public Map preTreatMmpay(String xml) throws DocumentException {
        logger.info("notify:" + xml);
        Map responseXmlMap = XMLUtil.parseXml(xml);
        Map responseDataMap = (Map) responseXmlMap.get("xml");  // 注意有最外层的<xml>标签
        if ("FAIL".equals(responseDataMap.get("return_code"))) {
            throw new RestfulException((String) responseDataMap.get("return_msg"));
        }
        if ("FAIL".equals(responseDataMap.get("result_code"))) {
            String err_code = (String) responseDataMap.get("err_code");
            String err_code_des = (String) responseDataMap.get("err_code_des");
            throw new RestfulException(err_code + "-" + err_code_des);
        }
        return responseDataMap;
    }

小程序用户信息保存

1、微信昵称可能包含表情,mysql 的字符编码得是utf8mb4,数据库连接url得加上 ?useUnicode=true&characterEncoding=utf8
mysql的 utf8 最大字符长度为 3 字节,utf8mb4则最大为4字节
2、头像的域名有 wx.qlogo.cn、thirdwx.qlogo.cn,小程序要下载头像存到临时路径,需要在微信后台设置这两个下载域名

小程序广告

eCPM:effective Cost Per Mile,每千次曝光的收益
ARPU:Average Revenue Per User,每用户平均收入
小程序广告eCPM:banner广告1-10,格子广告1-10,插屏广告5-50,视频广告2-20,激励式视频广告10-100,视频前贴广告(在有视频内容的小程序中才能使用)
广告收益总结:嵌入广告(banner\格子\视频)ARPU在0.5分左右,满屏广告(插屏/激励式视频)ARPU在5分左右
广告策略:在内容型、游戏型、工具型小程序中插入广告才有意义;在交易型小程序中,广告不能作为主要模式(例如看广告抽奖),仅能作为辅助收益(非满屏广告)

小商店

小程序小商店:与微信支付相比,微信支付T+6提现,小商店确认收货后可提现,发货15天后默认收货
个人小商店:与微店对比,小商店是微信官方出品,免服务费
企业小商店:与个人小商店相比,有电脑端管理后台
小程序跳小商店小程序:会有跳转提示,可以跳转到第三方小商店
小程序接入小商店组件:只能接入自己的小商店;组件只能在单独页面显示,不能嵌入到原小程序页面;商品订单等信息可以通过接口查询

阿里云

小程序云:一键在ECS上初始化后端运行环境(JAVA,Nodejs,Mysql等),提供Serverless服务

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容