docker-java 3.3.4 使用心得及示例

docker-java没有标准的文档,网上资料也零零散散,而且网上2.8版本和我使用的3.3版本有了不少变化,因此贴一点我使用的示例以供参考。

maven依赖:

            <dependency>
                <groupId>com.github.docker-java</groupId>
                <artifactId>docker-java-transport-httpclient5</artifactId>
                <version>3.3.4</version>
            </dependency>
            <dependency>
                <groupId>com.github.docker-java</groupId>
                <artifactId>docker-java</artifactId>
                <version>3.3.4</version>
            </dependency>
            <dependency>
                <groupId>org.glassfish.jersey.core</groupId>
                <artifactId>jersey-common</artifactId>
                <version>2.30.1</version>
            </dependency>

docker示例:

package cn.mx.docker.impl;

import cn.mx.common.utils.StringUtils;
import cn.mx.docker.DockerApiService;
import cn.mx.docker.vo.DockerContainerVO;
import cn.mx.docker.vo.LabelsOrKvVO;
import cn.mx.modules.resourcePool.handler.ResourcePoolSocketHandler;
import cn.mx.services.common.exceptions.CsbitException;
import com.alibaba.fastjson.JSONArray;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.LogContainerCmd;
import com.github.dockerjava.api.model.BlkioStatEntry;
import com.github.dockerjava.api.model.BlkioStatsConfig;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.CpuStatsConfig;
import com.github.dockerjava.api.model.ExposedPort;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.api.model.Image;
import com.github.dockerjava.api.model.Info;
import com.github.dockerjava.api.model.MemoryStatsConfig;
import com.github.dockerjava.api.model.Ports;
import com.github.dockerjava.api.model.StatisticNetworksConfig;
import com.github.dockerjava.api.model.Statistics;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.InvocationBuilder;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;

public class DockerApiServiceImpl {

    private final static Logger log = LoggerFactory.getLogger(DockerApiServiceImpl.class);

    
    public DockerClient createClient(String ip, Integer port) {
        String url = "tcp://" + ip + ":" + port;
        return DockerClientBuilder.getInstance(url).build();
    }

    
    public boolean testClientConn(String ip, Integer port) {
        DockerClient client = createClient(ip, port);
        Info exec = null;
        try {
            exec = client.infoCmd().exec();
        } catch (Exception e) {
            log.error("docker测试连接失败,ip为:{},端口为:{},失败原因为:{}", ip, port, e.getMessage());
            return false;
        }
        String serverVersion = exec.getServerVersion();
        if (!StringUtils.isEmpty(serverVersion)) {
            return true;
        }
        return false;
    }

    
    public String localImageToRemote(String localImagePath, String remoteIp, Integer remotePort) {
        String imageId = "";
        File file = new File(localImagePath);
        if (!file.exists()) {
            log.error("推送本地镜像到宿主机失败,本地镜像路径为:{},失败原因为该路径的镜像文件不存在", localImagePath);
            return null;
        }
        try {

            FileInputStream is = new FileInputStream(file);
            // 获取该镜像的id
            TarArchiveInputStream tin = new TarArchiveInputStream(is);
            TarArchiveEntry entry = tin.getNextTarEntry();
            String json = null;
            while (entry != null) {
                // 只读取manifest.json
                if (entry.getName().equals("manifest.json")) {
                    ByteArrayOutputStream result = new ByteArrayOutputStream();
                    int count;
                    byte[] data = new byte[1024];
                    while ((count = tin.read(data, 0, 1024)) != -1) {
                        result.write(data, 0, count);
                    }
                    result.close();
                    json = result.toString();
                    break;
                }
                entry = tin.getNextTarEntry();
            }
            // 不存在就直接返回失败
            if (json == null) {
                log.error("加载镜像到宿主机,镜像包中没有manifest.json文件,无法解析出镜像id,因此失败");
                return null;
            }
            imageId = JSONArray.parseArray(json).getJSONObject(0).getString("Config").replace(".json", "");
            // 关闭资源
            is.close();

            // 获取宿主机存在的镜像,如果已经存在则不再重新加载
            DockerClient docker = createClient(remoteIp, remotePort);
            List<Image> images = docker.listImagesCmd().exec();
            boolean isAlreadyExit = false;
            for (Image image : images) {
                // sha256:xxxxxxx
                if (image.getId().split(":")[1].equals(imageId)) {
                    isAlreadyExit = true;
                    break;
                }
            }

            if (!isAlreadyExit) {
                FileInputStream fis = new FileInputStream(file);
                // 加载镜像
                docker.loadImageCmd(fis).exec();
                fis.close();
            }

            docker.close();
        } catch (Exception e) {
            return null;
        }
        return imageId;
    }

    
    public String imageInfo(String imageId) {
        return null;
    }

    
    public List<Container> listContainer(Boolean showAll) {
        return null;
    }

    
    public List<String> logContainer(String remoteIp, Integer remotePort, String containerId) {
        List<String> logs = new ArrayList<>();
        DockerClient docker = createClient(remoteIp, remotePort);
        LogContainerCmd logContainerCmd = docker.logContainerCmd(containerId)
                .withStdOut(true)
                .withFollowStream(true) // 实时获取日志
//                .withTailAll() // 获取所有日志
                .withStdErr(true);
        InvocationBuilder.AsyncResultCallback<Frame> callback = new InvocationBuilder.AsyncResultCallback<Frame>() {
            @Override
            public void onNext(Frame object) {
                if (ResourcePoolSocketHandler.SESSIONS.get(containerId) == null || !ResourcePoolSocketHandler.SESSIONS.get(containerId).isOpen()) {
                    logContainerCmd.close();
                    try {
                        docker.close();
                    } catch (IOException e) {
                        log.error("关闭docker客户端失败,失败原因:{}", e.getMessage());
                    }
                    return;
                }
                // 实时通过webSocket传递日志
                ResourcePoolSocketHandler.sendMessage(containerId, object.toString());
                logs.add(object.toString());
            }
        };
        try {
            logContainerCmd.exec(callback).awaitCompletion();
        } catch (InterruptedException e) {
            log.error("docker获取容器日志失败,失败原因:{}", e.getMessage());
        }

        return logs;
    }

    
    public String containerInfo(String containerId) {
        return null;
    }

    
    public Boolean removeContainer(String remoteIp, Integer remotePort, String containerId) {
        try {
            DockerClient docker = createClient(remoteIp, remotePort);
            docker.removeContainerCmd(containerId).exec();
            return true;
        } catch (Exception e) {
            log.error("删除容器失败,远程服务器ip为:{},端口为:{},容器ID为:{},失败原因:{}", remoteIp, remotePort, containerId, e.getMessage());
            return false;
        }
    }

    
    public Boolean stopContainer(String remoteIp, Integer remotePort, String containerId) {
        try {
            DockerClient docker = createClient(remoteIp, remotePort);
            docker.stopContainerCmd(containerId).exec();
            return true;
        } catch (Exception e) {
            log.error("停止容器失败,远程服务器ip为:{},端口为:{},容器ID为:{},失败原因:{}", remoteIp, remotePort, containerId, e.getMessage());
            return false;
        }
    }

    
    public Boolean startContainer(String remoteIp, Integer remotePort, String containerId) {
        try {
            DockerClient docker = createClient(remoteIp, remotePort);
            docker.startContainerCmd(containerId).exec();
            return true;
        } catch (Exception e) {
            log.error("开启容器失败,远程服务器ip为:{},端口为:{},容器ID为:{},失败原因:{}", remoteIp, remotePort, containerId, e.getMessage());
            return false;
        }
    }

    
    public String createContainer(DockerContainerVO dockerContainerVO) {

        CreateContainerResponse response = null;
        try {
            DockerClient docker = createClient(dockerContainerVO.getRemoteIp(), dockerContainerVO.getRemotePort());

            Ports portBinding = new Ports();
            if (dockerContainerVO.getExposePorts() != null) {
                for (LabelsOrKvVO exposePort : dockerContainerVO.getExposePorts()) {
                    portBinding.bind(ExposedPort.tcp(Integer.parseInt(exposePort.getKey())), Ports.Binding.bindPort(Integer.parseInt(exposePort.getValue())));
                }
            }

            CreateContainerCmd containerCmd = docker.createContainerCmd(dockerContainerVO.getImageId());
            containerCmd.withImage(dockerContainerVO.getImageId())
                    .withName(dockerContainerVO.getContainerName());
            if (portBinding.getBindings() != null && !portBinding.getBindings().isEmpty()) {
                // 映射端口需要先将端口暴露出来再映射
                containerCmd.withExposedPorts(new ArrayList<>(portBinding.getBindings().keySet()));
                containerCmd.withPortBindings(portBinding);
            }
            // 同步宿主机的时间到容器
            containerCmd.withEnv("TZ=Asia/Shanghai");
            if (StringUtils.isNotEmpty(dockerContainerVO.getCmd())) {
                containerCmd.withCmd(dockerContainerVO.getCmd());
            }
            // 限制容器使用的cpu核心数
            if (StringUtils.isNotEmpty(dockerContainerVO.getCpus())) {
                containerCmd.withCpusetCpus(dockerContainerVO.getCpus());
            }
            // 限制容器使用的内存-默认单位字节,太难输,升级到MB
            if (dockerContainerVO.getMemory() != null && dockerContainerVO.getMemory() > 0) {
                containerCmd.withMemory(dockerContainerVO.getMemory() * 1024 * 1024);
            }
            response = containerCmd.exec();
        } catch (Exception e) {
            throw new CsbitException("创建容器失败,失败原因:" + e.getMessage());
        }
        if (StringUtils.isNotEmpty(response.getId())) {
            return response.getId();
        } else {
            throw new CsbitException("创建容器失败,失败原因:" + Arrays.toString(response.getWarnings()));
        }
    }

    
    public Boolean restartContainer(String remoteIp, Integer remotePort, String containerId) {
        try {
            DockerClient docker = createClient(remoteIp, remotePort);
            docker.restartContainerCmd(containerId).exec();
            return true;
        } catch (Exception e) {
            log.error("重启容器失败,远程服务器ip为:{},端口为:{},容器ID为:{},失败原因:{}", remoteIp, remotePort, containerId, e.getMessage());
            return false;
        }
    }

    public Map<String, String> getContainerResourceInfo(String remoteIp, Integer remotePort, String containerId) {
        DockerClient docker = createClient(remoteIp, remotePort);
        Map<String, String> map = new HashMap<>();
        InvocationBuilder.AsyncResultCallback<Statistics> callback = new InvocationBuilder.AsyncResultCallback<Statistics>() {
            @Override
            public void onNext(Statistics object) {

                CpuStatsConfig cpu = object.getCpuStats();
                CpuStatsConfig preCpu = object.getPreCpuStats();
                Long num1 = cpu.getCpuUsage().getTotalUsage() - preCpu.getCpuUsage().getTotalUsage();
                Long num2 = cpu.getSystemCpuUsage() - preCpu.getSystemCpuUsage();

                double cpuu = new BigDecimal(num1)
                        .multiply(new BigDecimal(cpu.getCpuUsage().getPercpuUsage().size() * 100))
                        .divide(new BigDecimal(num2), 2, RoundingMode.HALF_UP).doubleValue();
                map.put("cpu", cpuu + "%");

                MemoryStatsConfig memoryStatsConfig = object.getMemoryStats();

                double mem = new BigDecimal(memoryStatsConfig.getUsage()).multiply(new BigDecimal(100))
                        .divide(new BigDecimal(memoryStatsConfig.getLimit()), 2, RoundingMode.HALF_UP).doubleValue();

                map.put("memory", mem + "%");

                Map<String, StatisticNetworksConfig> networks = object.getNetworks();
                networks.forEach(new BiConsumer<String, StatisticNetworksConfig>() {
                    @Override
                    public void accept(String s, StatisticNetworksConfig statisticNetworksConfig) {
                        String networkI = "";
                        String networkO = "";
                        if (statisticNetworksConfig.getRxBytes() > 1024) {
                            networkI = (statisticNetworksConfig.getRxBytes() / 1024) + "KB";
                        } else {
                            networkI = statisticNetworksConfig.getRxBytes() + "B";
                        }
                        if (statisticNetworksConfig.getTxBytes() > 1024) {
                            networkO = (statisticNetworksConfig.getTxBytes() / 1024) + "KB";
                        } else {
                            networkO = statisticNetworksConfig.getTxBytes() + "B";
                        }
                        map.put("networks", networkI + "-" + networkO);
                    }
                });

                BlkioStatsConfig blkioStats = object.getBlkioStats();
                Long sumDiskI = 0L;
                Long sumDiskO = 0L;
                for (BlkioStatEntry blkioStatEntry : blkioStats.getIoServiceBytesRecursive()) {
                    if (blkioStatEntry.getOp().equals("Read")) {
                        sumDiskI += blkioStatEntry.getValue();
                    } else if (blkioStatEntry.getOp().equals("Write")) {
                        sumDiskO += blkioStatEntry.getValue();
                    }

                }
                String diskI = "";
                String diskO = "";
                if (sumDiskI > 1024) {
                    diskI = (sumDiskI / 1024) + "KB";
                } else {
                    diskI = sumDiskI + "B";
                }
                if (sumDiskO > 1024) {
                    diskO = (sumDiskO / 1024) + "KB";
                } else {
                    diskO = sumDiskO + "B";
                }
                map.put("diskIO", diskI + "-" + diskO);
            }
        };
        docker.statsCmd(containerId).withNoStream(true).exec(callback).awaitResult();
        try {
            docker.close();
        } catch (IOException e) {
            log.error("docker容器关闭失败,失败原因:{}", e.getMessage());
        }
        return map;
    }

    
    public List<Image> getImageListInfo(String remoteIp, Integer remotePort) {
        DockerClient docker = createClient(remoteIp, remotePort);
        List<Image> images = docker.listImagesCmd().exec();

        try {
            docker.close();
        } catch (IOException e) {
            log.error("关闭docker连接失败,失败原因:{}", e.getMessage());
        }
        return images;
    }

    public static void main(String[] args) {
        createContainerTest();
    }

    private static void loadImage() {
        try {
            File file = new File("D:\\xxx.tar");
            FileInputStream fis = new FileInputStream(file);

            // 加载镜像
            String url = "tcp://ip:2375";
            DockerClient docker = DockerClientBuilder.getInstance(url).build();
            docker.loadImageCmd(fis).exec();
            fis.close();

            FileInputStream is = new FileInputStream(file);
            // 获取该镜像的id
            TarArchiveInputStream tin = new TarArchiveInputStream(is);
            TarArchiveEntry entry = tin.getNextTarEntry();
            String json = null;
            while (entry != null) {
                // 只读取manifest.json
                if (entry.getName().equals("manifest.json")) {
                    ByteArrayOutputStream result = new ByteArrayOutputStream();
                    int count;
                    byte[] data = new byte[1024];
                    while ((count = tin.read(data, 0, 1024)) != -1) {
                        result.write(data, 0, count);
                    }
                    result.close();
                    json = result.toString();
                    break;
                }
                entry = tin.getNextTarEntry();
            }
            // 不存在就直接返回失败
            if (json == null) {
                log.error("加载镜像到宿主机,镜像包中没有manifest.json文件,无法解析出镜像id,因此失败");
            }
            String imageId = JSONArray.parseArray(json).getJSONObject(0).getString("Config").replace(".json", "");
            System.out.println(imageId);

            // 关闭资源
            is.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static void startContainerTest() {
        String url = "tcp://ip:2375";
        DockerClient docker = DockerClientBuilder.getInstance(url).build();
        docker.startContainerCmd("containerID").exec();
    }

    private static void deleteContainerTest() {
        String url = "tcp://ip:2375";
        DockerClient docker = DockerClientBuilder.getInstance(url).build();
        docker.removeContainerCmd("containerID").exec();
    }

    private static void containerInfo() {
        String url = "tcp://ip:2375";
        DockerClient docker = DockerClientBuilder.getInstance(url).build();
        System.out.println(111);
        InvocationBuilder.AsyncResultCallback<Statistics> callback = new InvocationBuilder.AsyncResultCallback<Statistics>() {
            @Override
            public void onNext(Statistics object) {

                CpuStatsConfig cpu = object.getCpuStats();
                CpuStatsConfig preCpu = object.getPreCpuStats();
                Long num1 = cpu.getCpuUsage().getTotalUsage() - preCpu.getCpuUsage().getTotalUsage();
                Long num2 = cpu.getSystemCpuUsage() - preCpu.getSystemCpuUsage();

                double cpuu = new BigDecimal(num1)
                        .multiply(new BigDecimal(cpu.getCpuUsage().getPercpuUsage().size() * 100))
                        .divide(new BigDecimal(num2), 2, RoundingMode.HALF_UP).doubleValue();


                MemoryStatsConfig memoryStatsConfig = object.getMemoryStats();

                double mem = new BigDecimal(memoryStatsConfig.getUsage()).multiply(new BigDecimal(100))
                        .divide(new BigDecimal(memoryStatsConfig.getLimit()), 2, RoundingMode.HALF_UP).doubleValue();
//                Integer mem = Math.round((memoryStatsConfig.getUsage() * 100 / memoryStatsConfig.getLimit()) * 100);

                Map<String, StatisticNetworksConfig> networks = object.getNetworks();
                networks.forEach(new BiConsumer<String, StatisticNetworksConfig>() {
                    @Override
                    public void accept(String s, StatisticNetworksConfig statisticNetworksConfig) {
                        System.out.println(s + ":");
                        System.out.println(statisticNetworksConfig.getRxBytes() + "-" + statisticNetworksConfig.getTxBytes() + "\n");
                    }
                });

                BlkioStatsConfig blkioStats = object.getBlkioStats();
                Long sumDiskI = 0L;
                Long sumDiskO = 0L;
                for (BlkioStatEntry blkioStatEntry : blkioStats.getIoServiceBytesRecursive()) {
                    if (blkioStatEntry.getOp().equals("Read")) {
                        sumDiskI += blkioStatEntry.getValue();
                    } else if (blkioStatEntry.getOp().equals("Write")) {
                        sumDiskO += blkioStatEntry.getValue();
                    }

                }
                System.out.println("sumDiskI:" + sumDiskI);
                System.out.println("sumDiskO:" + sumDiskO);
            }
        };
        docker.statsCmd("containerID").withNoStream(true).exec(callback).awaitResult();

        System.out.println("容器资源获取完成");
    }

    private static void createContainerTest() {
        String url = "tcp://ip:2375";
        DockerClient docker = DockerClientBuilder.getInstance(url).build();

//        List<PortBinding> ports = new ArrayList<>();
//        ports.add(PortBinding.parse("443:8443"));
//        HostConfig hostConfig = HostConfig.newHostConfig();
//        hostConfig.withPortBindings(ports);

        Ports portBinding = new Ports();
        portBinding.bind(ExposedPort.tcp(443), Ports.Binding.bindPort(8443));
        portBinding.bind(ExposedPort.tcp(990), Ports.Binding.bindPort(9090));


        CreateContainerResponse response = docker.createContainerCmd("imageID")
                .withImage("imageID")
                .withName("containerName")
                .withExposedPorts(ExposedPort.tcp(443), ExposedPort.tcp(990))
                .withPortBindings(portBinding)
//                .withCmd("--cpus", "1",
////                        "--storage-opt", "size=50G",
//                        "--memory", "3G")
                .withCmd("/usr/sbin/init") // docker save的命令
                .withCpusetCpus("1")
                .withMemory(1024 * 1024 * 1024 * 3L)
//                .withAuthConfig(authConfig)
                .exec();
        System.out.println("创建容器完成!");
        System.out.println(response.getId());
        System.out.println(Arrays.toString(response.getWarnings()));
    }

    private static void listImages() {
        String url = "tcp://ip:2375";
        DockerClient docker = DockerClientBuilder.getInstance(url).build();
//        List<SearchItem> items = docker.searchImagesCmd("9115afd2b62be67406aaaf856faa64600733421717618fb3c77d24d52d382f16").exec();
//        for (SearchItem item : items) {
//            System.out.println("item:" + item.getName());
//        }
        List<Image> images = docker.listImagesCmd().exec();
        for (Image image : images) {
            System.out.println("created:" + image.getCreated());
            System.out.println("size:" + image.getSize());
            if (image.getId().split(":")[1].equals("imageID")) {
                System.out.println("镜像已经上传");
            }
            for (String repoTag : image.getRepoTags()) {
                System.out.println(repoTag + ":" + image.getId());
            }
        }
    }

    private static void getContainerLog() {
        String url = "tcp://ip:2375";
        DockerClient docker = DockerClientBuilder.getInstance(url).build();
        System.out.println(111);
        LogContainerCmd logContainerCmd = docker.logContainerCmd("containerID")
                .withStdOut(true)
//                .withFollowStream(true)
                .withStdErr(true)
                .withTailAll();
        List<Object> logs = new ArrayList<>();
        InvocationBuilder.AsyncResultCallback<Frame> callback = new InvocationBuilder.AsyncResultCallback<Frame>() {
            @Override
            public void onNext(Frame object) {
                logs.add(object);
            }
        };
        try {
            logContainerCmd.exec(callback).awaitCompletion();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("logs的size:" + logs.size());
        for (Object o : logs) {
            System.out.println("logContent:" + o.toString());
        }
        System.out.println("日志获取完成");
    }

}

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

推荐阅读更多精彩内容