Ecology后端二次开发架构说明

1 二次开发现状分析

1.1 目前二次开发的痛点

  1. 过于依赖Ecology的JDK及Jar包,对于JDK及新版本工具jar包的使用具有局限性。

  2. 模块耦合度太高,代码关联性太强,变量修改容易造成全局污染,class代码升级需要停ecology服务,正式环境影响用户使用,测试环境影响项目及开发的工作效率。

  3. 二次开发的代码和标准产品代码的粘合度太高,无法提取独立的二次开发代码,不利于知识沉淀和后期复用,之前我们的代码复用大部分是类级复用而非模块级复用。

  4. 问题排查困难,由于代码和标准产品代码掺杂在一起,日志无法切割,排查困难。

  5. 对比产品开发,二次开发往往未经过针对性的压力或系统测试,二次开发引发的宕机往往会波及整个EC服务。

1.2 微服务的优势

  1. 不依赖于Ecology,JDK、Jar包版本无限制,利用Maven导入Java依赖,易于和第三方应用集成,支持使用不同的开发语言,允许整合最新的技术。

  2. 微服务是松耦合的,无论是开发阶段还是部署阶段都是独立的。

  3. 能够快速响应,局部修改容易,一个服务出现问题不会影响整个应用。

  4. Springboot有成熟的整合方案,对于主流的开发框架无配置集成。

  5. 内嵌Servlet容器,无需单独安装容器即可独立运行项目(默认是使用tomcat,整体以jar包的形式运行)。

  6. 利用SpringBoot整合Mybatis实现Sql统一管理和优化,简化Dao层代码编写,同时支持SQL热部署。

  7. 微服务可以使不同的团队专注于更小范围的工作职责、使用独立的技术、更安全更频繁地部署。

1.3 待解决问题

  1. 微服务代码版本管理及备案问题

  2. 微服务代码部署问题(每次都打一个比较大的jar包)

  3. 微服务环境启动问题(每次需要启动很多个应用服务和基础设施服务,初步考虑单独写启动脚本)

  4. Ecology的无侵入整合。

2 二次开发微服务架构说明

2.1 微服务二次开发架构图

必读:二次开发的微服务架构因项目而异,对于大型的项目可以按照功能模块拆分每个服务,只要涉及到多个微服务的情况必须搭建nacos和SpringCloudGetWay基础设施来保障EC到微服务、微服务到微服务之前的通信,反之项目体量不大、需求量不多则可以考虑抛弃nacos和springCloudGetWay(可以跳过本文章的2.4\2.5\2.6\2.7),只部署单体的SpringBoot服务即可

[图片上传失败...(image-a737a4-1593312061480)]

2.2 后端代码结构分层

[图片上传失败...(image-4680b4-1593312061480)]

2.2.1 结构****说明

|

包名

|

说明

|
|

entity

|

实体类层。与需要操作的表结构一致,生成set和get方法,使用Lomok减少大量的模板代码(IDE需要单独安装LomokPlugin)。(必须)

|
|

dao

|

数据库操作层。每个数据表的操作放在一个dao的中。Dao层的每个方法完成一个完整的数据库操作;如果操作包含多条sql,需要放在一个方法中,可以根据实际情况决定是否采用“事务”处理,该层只有方法名,方法名和Mapper.xml做一一对应。(必须)

|
|

service

|

业务服务层,调用Dao层。可以通过调用多个Dao类,组合业务操作的结果。Service层提供“方法代理”。(必须)

|
|

controller

|

控制层,用于和EC或View层通信。(必须)

|
|

aop

|

切面层,用于定义切面类,方便针对于二次开发代码进行无侵入修改。(非必须)

|
|

util

|

工具类。非必须,如果有需要独立编写的工具类,需要放在此处。(非必须)

|

2.3 构建第一个SpringBoot工程

2.3.1 创建工程

直接在Idea中Create New Project --> Spring Initializr --> 填写group、artifact -->钩上SpringWeb(SpringBoot版本建议选择2.3.0) --> 点下一步

[图片上传失败...(image-fc9aca-1593312061480)]

[图片上传失败...(image-e06e54-1593312061480)]

[图片上传失败...(image-5220ff-1593312061480)]

填写”ProjectName”后点击”Finish”,完成项目创建

[图片上传失败...(image-a78128-1593312061480)]

2.3.2 工程目录结构

|

-src

-main

    -java

        -package

            #主函数,启动类,运行它如果运行了 Tomcat、Jetty、Undertow 等容器

            -SpringbootApplication  

    -resouces

        #存放静态资源 js/css/images 等

        - statics

        #存放 html 模板文件

        - templates

        #主要的配置文件,SpringBoot启动时候会自动加载application.yml/application.properties      

        - application.properties

#测试文件存放目录       

-test

pom.xml 文件是Maven构建的基础,里面包含了我们所依赖JAR和Plugin的信息

  • pom.xml

|

2.3.3 调整工程目录

  1. 在src目录下新建包com.weavernorth.模块名

  2. 将IDE生成的SpringbootApplication.java文件放置在模块包的根目录下(注:由于Controller层、Dao层要和启动类有共同的父包,否则会出现扫描不到的问题,如果启动类和 controller 没有共同的父包,则需要在启动上增加@ComponentScan注解

  3. File->Project Structure->Modules将src设置为”Sources”,删除src/main下面的java文件夹。

[图片上传失败...(image-7079b8-1593312061480)]

  1. 修改application.properties后缀,改为application.yml。

  2. 在新建的“com.weavernorth.模块名”包下按照<u>“后端代码分层</u><u>”</u><u>(2.2)</u>创建java分层路径,最终效果如下:

[图片上传失败...(image-9599d2-1593312061479)]

2.3.4 修改maven配置

IEDA中, File--->Settings--->Maven

查看当前的settings.xml文件路径。

[图片上传失败...(image-82ab57-1593312061479)]

打开settings.xml文件,将下图的maven源配置进settings.xml文件中

<mirrors>

<mirror>  

    <id>nexus-aliyun</id>  

    <mirrorOf>central</mirrorOf>    

    <name>Nexus aliyun</name>  

    <url>http://maven.aliyun.com/nexus/content/groups/public</url>  

</mirror> 

</mirrors>

2.3.5 pom.xml

在创建Springboot工程时自动生成好,不需要更改,以下为示例:

|

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

     xsi:schemaLocation="[http://maven.apache.org/POM/4.0.0](http://maven.apache.org/POM/4.0.0) [https://maven.apache.org/xsd/maven-4.0.0.xsd](https://maven.apache.org/xsd/maven-4.0.0.xsd)">  

<modelVersion>4.0.0</modelVersion>  

<parent>  

    <groupId>org.springframework.boot</groupId>  

    <artifactId>spring-boot-starter-parent</artifactId>  

    <version>2.3.0.RELEASE</version>  

    <relativePath/> <!-- lookup parent from repository -->  

</parent>  

<groupId>com.weavernorth</groupId>  

<artifactId>salary</artifactId>  

<version>0.0.1-SNAPSHOT</version>  

<name>salary</name>  

<description>The First SpringBoot Project</description>  

<properties>  

    <java.version>1.8</java.version>  

</properties>  

<dependencies>  

    <dependency>  

        <groupId>org.springframework.boot</groupId>  

        <artifactId>spring-boot-starter-web</artifactId>  

    </dependency>  

    <dependency>  

        <groupId>org.springframework.boot</groupId>  

        <artifactId>spring-boot-starter-test</artifactId>  

        <scope>test</scope>  

        <exclusions>  

            <exclusion>  

                <groupId>org.junit.vintage</groupId>  

                <artifactId>junit-vintage-engine</artifactId>  

            </exclusion>  

        </exclusions>  

    </dependency>  

</dependencies>  

<build>  

    <plugins>  

        <plugin>  

            <groupId>org.springframework.boot</groupId>  

            <artifactId>spring-boot-maven-plugin</artifactId>  

        </plugin>  

    </plugins>  

</build>  

</project>

|

2.3.6 创建实体类

com/weavernorth/salary/entity/Salary.java(建议使用Lomok生成setter/getter)

package com.weavernorth.salary.entity;

import org.springframework.stereotype.Component;

/**

  • 工资单

*/

@Component

public class Salary {

private int Hrmid;

private String HrmName;

//实发工资

private Double doubleNetSalary;

//发放日期

private String strDate;

public int getHrmid() {

    return Hrmid;

}

public void setHrmid(int hrmid) {

    Hrmid = hrmid;

}

public String getHrmName() {

    return HrmName;

}

public void setHrmName(String hrmName) {

    HrmName = hrmName;

}

public Double getDoubleNetSalary() {

    return doubleNetSalary;

}

public void setDoubleNetSalary(Double doubleNetSalary) {

    this.doubleNetSalary = doubleNetSalary;

}

public String getStrDate() {

    return strDate;

}

public void setStrDate(String strDate) {

    this.strDate = strDate;

}

}

2.3.7 创建Controller

com/weavernorth/salary/controller/SalaryController.java

package com.weavernorth.salary.controller;

import com.weavernorth.salary.entity.Salary;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.*;

@RestController

@RequestMapping(value = "/api/weavernorth/hrm")

public class SalaryController {

@Autowired

Salary salary;

/**

 * 计算工资

 * @param hrmid 人员id

 * @param NetSalary 实发工资

 * @param Date 发放日期

 * @return

 */

@RequestMapping(value = "/salary/computeSalary",method= RequestMethod.GET)

public Salary computeSalary(@RequestParam int hrmid,@RequestParam Double NetSalary,@RequestParam String Date){

    salary.setHrmid(hrmid);

    salary.setDoubleNetSalary(NetSalary);

    salary.setStrDate(Date);

    return salary;

}

}

2.3.8 Maven打包

1.在IDE最右边的侧栏找到”Maven Projects”

[图片上传失败...(image-2b51f6-1593312061478)]

  1. 打包步骤clean->重新编译src->install,install成功后会在控制台中显示”BUILD SUCCESS”成功标志,在target目录下会生成一个[artifactId]+[version].jar格式的jar包,此jar包则为我们打包好的微服务应用。

[图片上传失败...(image-78c3fa-1593312061478)]

2.3.9 启动工程

2.3.9.1 本地启动

方法一:直接运行SpringBootFirstAppApplication中的main方法

|

package com.weavernorth.salary;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class SalaryApplication {

public static void main(String[] args) {

    SpringApplication.run(SalaryApplication.class, args);

}

}

|

启动类的mian()方法中调用了Spring Boot的SpringApplication.run() 方法来启动一个应用。不同于以往的web应用,Spring Boot应用没有一行xml配置,也没有web.xml文件。

[图片上传失败...(image-2fc4f9-1593312061478)]

方法二:以java -jar的方式启动

maven打包完成后cd到target目录下执行以下命令,jar包名称视自己情况而定

java -jar salary-0.0.1-SNAPSHOT.jar

2.3.9.2 客户生成环境启动(linux)

windows系统启动参考 2.3.9.1

Linux环境:

(1) 确保打包无误后,放到指定目录(目录随意,不一定是lib)下

[图片上传失败...(image-1b1e40-1593312061478)]

(2) 进入打包目录下执行:

nohup java -jar loongson-0.0.1-SNAPSHOT.jar &

[图片上传失败...(image-72321b-1593312061478)]

21530是进程号

(3) 停止服务

运行成功后,查看进程 ps -aux | grep java

[图片上传失败...(image-1ecbea-1593312061478)]

杀掉进程 kill -9 21530(进程号)

2.3.10 访问工程

1.未在application.properties或application.yml设置指定端口号时,springboot应用默认端口为8080。

2.访问http://localhost:8080出现以下页面表示服务启动正常。

[图片上传失败...(image-b2e984-1593312061478)]

  1. 访问我们刚才发布的rest接口并传入参数(<u>http://localhost:8080/api/weavernorth/hrm/salary/computeSalary?hrmid=12&NetSalary=2000.0&Date=2020-05-02</u>),页面打印我们传入的参数表示接口测试成功。

[图片上传失败...(image-8439b1-1593312061478)]

2.4 SpringBoot整合Mybatis+Druid+log4j(必要)

2.4.1 添加依赖

1.在 pom.xml 中添加mybatis、Druid、sqlserver、log4j依赖

|

<dependency>

<groupId>org.springframework.boot</groupId>  

<artifactId>spring-boot-starter-jdbc</artifactId> 

</dependency>

<dependency>

<groupId>com.microsoft.sqlserver</groupId>  

<artifactId>mssql-jdbc</artifactId>  

<scope>runtime</scope> 

</dependency>

<dependency>

<groupId>org.mybatis.spring.boot</groupId>  

<artifactId>mybatis-spring-boot-starter</artifactId>  

<version>1.3.2</version> 

</dependency>

<dependency>

<groupId>com.alibaba</groupId>  

<artifactId>druid-spring-boot-starter</artifactId>  

<version>1.1.10</version> 

</dependency>

<dependency>

<groupId>log4j</groupId>  

<artifactId>log4j</artifactId>  

<version>1.2.17</version> 

</dependency>

|

2.4.2 application.yml相关配置

数据库信息

spring:

application:

name: salary

datasource:

url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ecology2019

driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver

username: sa

password: 123456

#采用Druid连接池

type: com.alibaba.druid.pool.DruidDataSource

log4j

logging:

path: ./log

mybatis

mybatis:

mapper扫描映射文件位置

mapper-locations: classpath:config/mappers/weavernorth//.xml

configuration:

log-impl: org.apache.ibatis.logging.log4j.Log4jImpl

注意:如果将mybatis.mapper-locations配置为classpath:config/mappers//.xml即mapper接口所在的包路径下,而Spring Boot默认只打入java package -> *.java,所以我们需要给pom.xml文件添加以下内容

 <build>

    <resources>

        <resource>

            <directory>src/main/resources</directory>

        </resource>

        <resource>

            <directory>src/main/java</directory>

            <includes>

                <include>**/*.xml</include>

            </includes>

            <filtering>true</filtering>

        </resource>

    </resources>

    <plugins>

        <plugin>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-maven-plugin</artifactId>

            <configuration>

                <includeSystemScope>true</includeSystemScope>

            </configuration>

        </plugin>

    </plugins>

</build>

2.4.3 设置log4j配置文件

在src/main/resource文件夹下新建log4j.properties文件复制以下代码

log4j.rootLogger=DEBUG,Console

log4j.appender.Console=org.apache.log4j.ConsoleAppender

log4j.appender.Console.layout=org.apache.log4j.PatternLayout

log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

log4j.logger.org.apache=INFO

log4j.logger.com.weavernorth.salary.dao=DEBUG

2.4.4 编码实战

2.4.4.1 需求简介

以Hrmresource为例,我们的目标是使用Mybatis查询出Hrmresource中Id、loginid、sex、lastname字段映射到我们的User对象中,并展示在View层。

2.4.4.2 表结构

[图片上传失败...(image-65ce67-1593312061477)]

2.4.4.3 创建实体类

com/weavernorth/Salary/entity/User.java(建议使用Lomok生成setter/getter)

package com.example.springbootmybatis.entity;

import lombok.Data;

import org.springframework.stereotype.Component;

import javax.persistence.Id;

@Component

@Data

public class User {

/** 用户id */

@Id

private Integer id;

/** 用户名 */

private String loginid;

/** 性别 */

private String sex;

/** 姓名 */

private String lastname;

}

2.4.4.4 创建Dao类

com/weavernorth/Salary/dao/IUserDaoService.java

|

package com.weavernorth.salary.dao;

import com.weavernorth.salary.entity.User;

import org.springframework.stereotype.Repository;

import java.util.List;

@Repository

public interface IUserDaoService {

List<User> findAllUser();

}

|

2.4.4.5 创建Mapper配置文件

创建mapper的xml配置文件,在/src/main/resources/config/mappers/weavernorth(所有的mapper都存放在该目录下,由于前面我们在application.yml中设置了mybatis.mapper-locations属性,springboot会自动注册我们设置路径下的mapper,如果不放到该路径下则mapper无法注册到Mybatis中,****必要)路径下创建Mapper文件:User.xml

Mapper中包含Dao类中所有需要执行的查询语句,命名空间要使用对应Dao类的java路径,sqlID和Dao类中的方法名需要一致。

注意:原则上每一个Dao类对应至少一个mapper文件,mapper的路径分层尽量和Dao类的包功能层一致。

例如****IUserDaoService****类存在于****”****src/com/****weavernorth****/****salary****/dao****”****下,则mapper则创建在****”****src/main/resources****/config/mapper****s****/weavernorth/****salary****”

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper

    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

    "[http://mybatis.org/dtd/mybatis-3-mapper.dtd](http://mybatis.org/dtd/mybatis-3-mapper.dtd)">

<mapper namespace="com.weavernorth.salary.dao.IUserDaoService">

<select id="findAllUser" resultType="com.weavernorth.salary.entity.User" parameterType="string">

SELECT id,loginid,lastname,sex FROM HRMRESOURCE

</select>

</mapper>

2.4.4.6 创建JunitTest类测试

|

package com.weavernorth.salary.dao;

import com.weavernorth.salary.dao.IUserDaoService;

import com.weavernorth.salary.entity.User;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@SpringBootTest

@RunWith(SpringRunner.class)

public class IUserMapperTest {

@Autowired

User user;

@Autowired

IUserDaoService iUserDaoService;

private static final Logger logger = LoggerFactory.getLogger(IUserMapperTest.class);

@Test

public void findAllUsers(){

    List<User> users=iUserDaoService.findAllUser();

    logger.info(users.toString());

}

}

|

2.4.4.7 执行结果查看

运行IUserMapperTest.java,查看Console中打印以下日志代表MyBatis整合成功。

[图片上传失败...(image-13ee3a-1593312061476)]

2.4.5 Mapper.xml的属性及标签说明

2.4.5.1 常见属性

|

属性

|

作用

|
|

namespace

|

Dao类对应接口的路径

|
|

id

|

表示此段sql执行语句的唯一标识,也是接口的方法名称【必须一致才能找到方法】

|
|

parameterType

|

表示该sql语句中需要传入的参数, 类型要与对应的接口方法的类型一致【可选】

|
|

resultMap

|

定义出参,调用已定义的映射管理器的id值

|
|

resultType

|

定义出参,匹配普通Java类型或自定义的pojo【出参类型若不指定,将为语句类型默认类型,如语句返回值为int】

|

2.4.5.2 ${}和#{}的区别

{}会自动在你要插入字段两端 加上引号。例如:你写的是order by #{username},传的是 liujun,那么会解析成order by “liujun”。

{}是将传入的数据直接显示生成在sql中。如:order by{user_id},如果传入的值是111,那么解析成sql时的值为order by 111 如果传入的值是id,则解析成的sql为order by id.

{}: 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,一个 #{ } 被解析为一个参数占位符 。

{}: 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换。在使用order by 时,就需要使用

2.4.5.3 <sql>标签

该标签主要定义复用的sql语句片段,在执行的sql语句标签直接引用即可。可以提高编码效率、简化代码和提高可读性。

需要配置id熟悉,表示该sql片段的唯一标识。

引用:通过<include refid=" " />标签引用,refid的值就是<sql>的id属性的值。

  1. <sql id="Base_Column_List">

  2. id, question, answer   
    
  3. </sql>

  4. <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">

  5. select   
    
  6. **<include** refid="Base_Column_List" **/>**  
    
  7. from java  
    
  8. where id = #{id,jdbcType=BIGINT}  
    
  9. </select>

在<sql>中也可以include其他的<sql>标签,例如:

  1. <sql id="sometable">

  2. ${prefix}Table   
    
  3. </sql>

  4. <sql id="someinclude">

  5. from   
    
  6.     **<include** refid="${include_target}"**/>**   
    
  7. </sql>

  8. <select id="select" resultType="map">

  9. select   
    
  10.     field1, field2, field3   
    
  11. **<include** refid="someinclude"**>**   
    
  12.     **<property** name="prefix" value="Some"**/>**   
    
  13.     **<property** name="include_target" value="sometable"**/>**   
    
  14. **</include>**   
    
  15. </select>

2.4.5.4 <where>和<if>标签

<where> : 主要用来替换sql语句中的where字段,他的作用主要是用来简化sql语句中where条件判断的书写的

<if>:条件判断标签,配置属性test=" 条件字符串 ",判断是否满足条件,满足则执行,不满足则跳过。

如果当id值为空时,此时打印的sql应是:select * from hrmresource where name=“xx” and age=“xx”

where 标签会自动将其后第一个条件的and或者是or给忽略掉

  1. <select id="selectByParams" parameterType="map" resultType="user">

  2. select * from hrmresource

  3. <where>

  4. <if test="id != null ">id=#{id}</if>

  5. <if test="name != null and name.length()>0" >and name=#{name}</if>

  6. <if test="age != null and age.length()>0">and age = #{age}</if>

  7. </where>

  8. </select>

2.4.5.5 <set>标签

<set> : 主要用来替换sql语句中的set字段,一般在update中使用。

在下述的代码片段当中,假如说现在三个字段都有值得话,那么上面打印的SQL语句如下:

update hrmresource set name=‘xxx’ , age=‘xx’ where id=‘x’

在上面age="xx"的后是没有逗号的,也就是说set标记已经自动帮助我们把最后一个逗号给去掉了

set 标记会自动将其后第一个条件后的逗号忽略掉

  1. <update>

  2. update hrmresource

  3. <set>

  4. **<if** test="name != null and name.length()>0"**>**name = #{name},**</if>**  
    
  5. **<if** test="age != null and age .length()>0"**>**age = #{age },**</if>**  
    
  6. </set>

  7. where id = #{id}

  8. </update>

2.4.5.6 <trim>标签

<trim> : 是一个格式化的标记,可以完成set或者是where标记的功能。

示例1:

  1. select * from hrmresource

  2. <trim prefix="WHERE" prefixoverride="AND |OR">

  3. <if test="name != null and name.length()>0"> AND name=#{name}</if>

  4. <if test="age != null and age.length()>0"> AND age=#{age}</if>

  5. </trim>

假如说name和age的值都不为null的话打印的SQL为:select * from hrmresource where name = ‘xx’ and age = ‘xx’

在where的后面是不存在第一个and的,上面两个属性的意思如下:

prefix:前缀

prefixoverride:去掉第一个and或者是or

示例2:

  1. update hrmresource

  2. <trim prefix="set" suffixoverride="," suffix=" where id = #{id} ">

  3. <if test="name != null and name.length()>0"> name=#{name} , </if>

  4. <if test="age!= null and age.length()>0"> age=#{age} , </if>

  5. </trim>

假如说name和age的值都不为null的话打印的SQL为:update hrmresource set name=‘xx’ , age=‘xx’ where id=‘x’

在age='xx’的后面不存在逗号,而且自动加了一个set前缀和where后缀,上面三个属性的意义如下,其中prefix意义如上:

suffixoverride:去掉最后一个逗号(也可以是其他的标记,就像是上面前缀中的and一样)

suffix:后缀

2.4.5.7 <choose>标签

<choose> : choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql。类似于Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。

  1. <select id="selectByParams" parameterType="map" resultType="user">

  2. select * from hrmresource where 1 = 1

  3. <choose>

  4.         **<when** test="id !=null "**>**    
    
  5.             AND id = #{id}  
    
  6.         **</when** **>**    
    
  7.         **<when** test="username != null and username != '' "**>**    
    
  8.             AND username = #{username}    
    
  9.         **</when** **>**    
    
  10.         **<when** test="age != null and age !=''"**>**    
    
  11.             AND age = #{age}    
    
  12.         **</when** **>**    
    
  13.         **<otherwise>**    
    
  14.         **</otherwise>**    
    
  15.          **</choose>**  
    
  16. </select>

2.5 SpringBoot整合Nacos(非必要)

2.5.1 下载Nacos

https://github.com/alibaba/nacos/releases

[图片上传失败...(image-8eeae3-1593312061473)]

Linux版本: nacos-server-1.2.1.tar.gz

Windows版本: nacos-server-1.2.1.zip

2.5.2 启动nacos

解压后进入nacos/bin目录,执行startup.cmd

jdk必须是1.8以上,并且是 64位

如果启动成功,Nacos在8848端口绑定管理端Web应用程序,例如在本机运行则入口是http://localhost:8848/nacos/,默认用户名和密码都是nacos。

闪退情况:

右键用编辑器打开startup.cmd

[图片上传失败...(image-92ac45-1593312061473)]

[图片上传失败...(image-ec2f22-1593312061473)]

本地配置的环境变量JAVA_HOME,不能以bin目录结尾。

2.5.3 引入Nacos

本章将描述在nacos中如何注册服务、参数动态配置

2.5.3.1 Nacos配置中心

开发payment-service服务。

2.5.3.1.1 引入依赖

<properties>

    <java.version>1.8</java.version>

    <spring-cloud.version>Greenwich.SR1</spring-cloud.version>

    <alibaba.version>0.9.0.RELEASE</alibaba.version>

</properties>

<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>

        <version>${alibaba.version}</version>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-test</artifactId>

        <scope>test</scope>

    </dependency>

</dependencies>

<dependencyManagement>

    <dependencies>

        <dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-dependencies</artifactId>

            <version>${spring-cloud.version}</version>

            <type>pom</type>

            <scope>import</scope>

        </dependency>

    </dependencies>

</dependencyManagement>
2.5.3.1.2 编写Controller

@RestController

@RefreshScope

public class PaymentController {

@Value("${sleep:0}")

private int sleep;

final static Map<Integer, Balance> balanceMap = new HashMap() {{

    put(1, new Balance(1, 10, 1000));

    put(2, new Balance(2, 0, 10000));

    put(3, new Balance(3, 100, 0));

}

};

@RequestMapping("/pay/balance")

public Balance getBalance(Integer id) {

    System.out.println("request: /pay/balance?id=" + id + ", sleep: " + sleep);

    if(sleep > 0) {

        try {

            Thread.sleep(sleep);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

    if(id != null && balanceMap.containsKey(id)) {

        return balanceMap.get(id);

    }

    return new Balance(0, 0, 0);

}

}

2.5.3.1.3 实体类

public class Balance {

private int id;

private int diamond;

public int getId() {

    return id;

}

public void setId(int id) {

    this.id = id;

}

public int getDiamond() {

    return diamond;

}

public void setDiamond(int diamond) {

    this.diamond = diamond;

}

public int getTicket() {

    return ticket;

}

public void setTicket(int ticket) {

    this.ticket = ticket;

}

public String getMessage() {

    return message;

}

public void setMessage(String message) {

    this.message = message;

}

private int ticket;

private String message;

public Balance() {

}

public Balance(int id, int diamond, int ticket) {

    this(id, diamond, ticket, "OK");

}

public Balance(int id, int diamond, int ticket, String message) {

    this.id = id;

    this.diamond = diamond;

    this.ticket = ticket;

    this.message = message;

}

}

2.5.3.1.4 启动类

@SpringBootApplication

public class SalaryApplication {

public static void main(String[] args) {

    SpringApplication.run(SalaryApplication.class, args);

}

}

2.5.3.1.5 配置文件

application.yml文件中添加下图信息:

|

spring:

profiles:

active: dev

server:

port: 8082

sleep: 0

|

port:服务的端口号

创建bootstrap.yml文件,内容如下:

其中 server-addr即Nacos的IP和端口

spring: application: name: payment-service cloud: nacos: config: server-addr: 127.0.0.1:8848

Name:服务名称

Server-addr:监听nacos的ip及端口号

2.5.3.1.6 启动服务

日志中见如下信息,代表配置成功

[图片上传失败...(image-cb21a3-1593312061469)]

访问:http://localhost:8082/pay/balance?id=1

[图片上传失败...(image-59b850-1593312061469)]

2.5.3.1.7 创建nacos配置项动态修改配置信息

访问nacos:http://localhost:8848/nacos

[图片上传失败...(image-154fad-1593312061469)]

[图片上传失败...(image-22ca19-1593312061469)]

Data ID命名规则:

  **application.name**-dev.properties

然后重新调用接口,查看输出日志

[图片上传失败...(image-ada85f-1593312061469)]

然后****将配置改为2000发布后再****调用接口,查看输出日志

[图片上传失败...(image-7d5ccd-1593312061469)]

[图片上传失败...(image-7d7c1f-1593312061469)]

[图片上传失败...(image-aca94b-1593312061469)]

[图片上传失败...(image-50215a-1593312061469)]

2.5.3.2 Nacos注册中心

将payment-service服务注册进nacos

2.5.3.2.1 添加依赖

<dependency>

    <groupId>org.springframework.cloud</groupId>

    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

    <version>${alibaba.version}</version>

</dependency>

2.5.3.2.2 修改bootstrap.yml

pring:

application:

name: payment-service

cloud:

nacos:

  config:

    server-addr: 127.0.0.1:8848

  discovery:

    server-addr: 127.0.0.1:8848
2.5.3.2.3 启动类上添加注解@EnableDiscoveryClient

@SpringBootApplication

@EnableDiscoveryClient

public class SalaryApplication {

public static void main(String[] args) {

    SpringApplication.run(SalaryApplication.class, args);

}

}

2.5.3.2.4 启动服务并查看nacos服务列表

重启服务,查看输出日志:

[图片上传失败...(image-60c2f8-1593312061469)]

[图片上传失败...(image-3b5c77-1593312061469)]

2.5.3.2.5 执行mvc clean package把应用打成jar包并在8082和8083端口上启动两个实例

|

java -jar mynacosdemo-0.0.1-SNAPSHOT.jar --server.port=8082

java -jar mynacosdemo-0.0.1-SNAPSHOT.jar --server.port=8083

|

回到Nacos后台查看,可以看到两个实例信息

[图片上传失败...(image-336b81-1593312061469)]

点击详情:

[图片上传失败...(image-c1ddc7-1593312061469)]

停止payment-service服务后,健康实例数变为0

[图片上传失败...(image-1caf32-1593312061469)]

[图片上传失败...(image-731295-1593312061469)]

至此,服务Provider的工作完成

2.6 SpringBoot整合Feign(非必要)

本章将结合具体的应用场景描述如何实现服务之间的相互调用

场景:

payment-service服务(参照2.5.3.2):

   此服务中存储了每个人对应的账户余额。

account-service服务:

   此服务中存储了人员信息。

在调用account-service服务的接口时,要同时获取人员信息及每个人对应的账户余额。

也就是说,account-service服务要调用payment-service服务的接口(使用Feign)。

2.6.1 account-service服务

新建名称为account-service的SpringBoot应用,配置文件与payment-service基本相同,仅修改 以下两项,服务端口号和服务名称:

application.yml

server.port=8081

bootstrap.yml

spring.application.name=account-service

2.6.1.1 添加依赖

<properties>

    <java.version>1.8</java.version>

    <spring-cloud.version>Greenwich.SR1</spring-cloud.version>

    <alibaba.version>0.9.0.RELEASE</alibaba.version>

    <spring-cloud-netflix.version>2.1.1.RELEASE</spring-cloud-netflix.version>

    <spring-cloud-openfeign.version>2.1.1.RELEASE</spring-cloud-openfeign.version>

</properties>

<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>

        <version>${alibaba.version}</version>

    </dependency>

    <dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

        <version>${alibaba.version}</version>

    </dependency>

    <dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-openfeign</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-test</artifactId>

        <scope>test</scope>

    </dependency>

</dependencies>

<dependencyManagement>

    <dependencies>

        <dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-dependencies</artifactId>

            <version>${spring-cloud.version}</version>

            <type>pom</type>

            <scope>import</scope>

        </dependency>

        <dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-openfeign-dependencies</artifactId>

            <version>${spring-cloud-openfeign.version}</version>

            <type>pom</type>

            <scope>import</scope>

        </dependency>

    </dependencies>

</dependencyManagement>

2.6.1.2 编写Controller

@RestController

public class AccountController {

final static Map<Integer, User> userMap = new HashMap() {{

    put(1, new User(1, "张三"));

    put(2, new User(2, "李四"));

    put(3, new User(3, "王五"));

}

};

@Autowired

private BalanceService balanceService;

@RequestMapping("/acc/user")

public User getUser(@RequestParam Integer id) {

    if(id != null && userMap.containsKey(id)) {

        User user = userMap.get(id);

        //service中去调用 另外的服务接口 获取余额

        Balance balance = balanceService.getBalance(id);

        user.setBalance(balance);

        return user;

    }

    return new User(0, "");

}

}

2.6.1.3 编写service

编写BalanceService接口及实现类

接口:

@FeignClient(name = "payment-service", fallback = BalanceServiceImpl.class)

public interface BalanceService {

@RequestMapping(value = "/pay/balance", method = RequestMethod.GET)

Balance getBalance(@RequestParam("id") Integer id);

}

FeignClient:

name:调用服务的名称

fallback:指定一个实现Feign接口的实现类

RequestMapping****:

value:被调用服务的接口地址

实现类:

@Component

public class BalanceServiceImpl implements BalanceService {

@Override

public Balance getBalance(Integer id) {

    return new Balance(0, 0, 0, "降级");

}

}

2.6.1.4 启动account-service服务

浏览器访问:http://localhost:8081/acc/user?id=1

此时在浏览器中可以看到余额信息,余额信息是从payment-service中获取

[图片上传失败...(image-2c96a9-1593312061466)]

查看payment-service服务的输出日志:

[图片上传失败...(image-c0b142-1593312061466)]

2.6.1.5 灾难测试

关闭一个payment-service服务,然后访问:http://localhost:8081/acc/user?id=1

发现服务仍然可用

[图片上传失败...(image-6553e-1593312061466)]

将两个payment-service都关闭,然后访问:http://localhost:8081/acc/user?id=1

发现报错

[图片上传失败...(image-ebc304-1593312061466)]

解决:

在account-service中添加依赖:

<dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>

        <version>${alibaba.version}</version>

</dependency>

在bootstrap.yml中添加

feign:

sentinel:

enabled: true

打开sentinel对Feign的支持

此时重启account-service服务

payment-service两个服务仍然处于关闭状态

访问:http://localhost:8081/acc/user?id=1

此时发现没有报错,而是返回了默认的余额信息

[图片上传失败...(image-dd63af-1593312061466)]

2.7 SpringBoot整合SpringCloudGetWay(非必要)

Nacos仅仅提供了注册中心实现了单服务的多实例化,如果想要实现负载均衡,那么还要依赖GetWay的路由和分流

2.7.1 首先分不同端口启动两个微服务实例,见2.5.4.5

2.7.2 路由

2.7.2.1 创建新的Spring boot项目

2.7.2.2 添加依赖

注意: Spring boot 版本 和 Spring cloud gatewaty的版本必须保持一致

<properties>

    <java.version>1.8</java.version>

    <spring-cloud.version>Greenwich.SR1</spring-cloud.version>

    <alibaba.version>0.9.0.RELEASE</alibaba.version>

</properties>

<dependencies>

    <dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-gateway</artifactId>

        <version>2.1.3.RELEASE</version>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-data-redis</artifactId>

        <version>1.4.0.RELEASE</version>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>

    </dependency>

    <dependency>

        <groupId>redis.clients</groupId>

        <artifactId>jedis</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>

        <version>${alibaba.version}</version>

    </dependency>

    <dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>

        <version>${alibaba.version}</version>

    </dependency>

    <dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-test</artifactId>

        <scope>test</scope>

    </dependency>

</dependencies>

<dependencyManagement>

    <dependencies>

        <dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-dependencies</artifactId>

            <version>${spring-cloud.version}</version>

            <type>pom</type>

            <scope>import</scope>

        </dependency>

    </dependencies>

</dependencyManagement>

2.7.2.3 修改bootstrap.yml文件

spring:

application:

name: gateway

cloud:

nacos:

  config:

    server-addr: 127.0.0.1:8848

  discovery:

    server-addr: 127.0.0.1:8848

gateway:

  discovery:

    locator:

      enabled: true

  routes:

    - id: payment-router

      uri: lb://payment-service

      predicates:

        - Path=/pay/**

id: payment-router (值随意,方便记忆并且在所有路由定义中唯一即可)

uri: lb://payment-service

lb://为固定写法,表示开启负载均衡;payment-service即服务在Nacos中注册的名字

predicates:- Path=/pay/** (使用"Path Route Predicate Factory",规则为/pay开头的任意URI)

2.7.2.4 修改application.yml文件

端口号改为8084

spring:

profiles:

active: dev

server:

port: 8084

sleep: 0

2.7.2.5 启动类添加****@EnableDiscoveryClient****注解

@SpringBootApplication

@EnableDiscoveryClient

public class GetawayApplication {

public static void main(String[] args) {

    SpringApplication.run(GetawayApplication.class, args);

}

}

2.7.2.6 启动应用测试

启动刚创建的Spring boot项目

浏览器访问http://localhost:8084/pay/balance?id=2

[图片上传失败...(image-19c353-1593312061464)]

请求成功,可以看出请求被分发到了 payment-service1和payment-service2中

同时查看两个payment服务的输出日志,并发起6次请求

可以看出多次请求被平均分配到两个实例上

[图片上传失败...(image-7a42ab-1593312061464)]

2.7.2.7 Path Route Predicate Factory

除了Path Route Predicate Factory,Gateway还支持多种设置方式:

|

类型

|

示例:

|
|

After

|

After=2017-01-20T17:42:47.789-07:00[America/Denver]

|
|

Before

|

Before=2017-01-20T17:42:47.789-07:00[America/Denver]

|
|

Between

|

2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

|
|

Cookie

|

Cookie=chocolate, ch.p

|
|

Header

|

Header=X-Request-Id, \d+

|
|

Host

|

Host=**.somehost.org

|
|

Method

|

Method=GET

|
|

Path

|

Path=/foo/{segment}

|
|

Query

|

Query=baz

|
|

RemoteAddr

|

RemoteAddr=192.168.1.1/24

|

2.7.3 GetWay结合Nacos实现动态路由

2.7.3.1 创建监听类

Spring Cloud Gateway本身还不支持直接从Nacos动态加载路由配置表,需要自己编写监听器监听配置变化并刷新路由表。

NacosDynamicRouteService.java

|

package com.example.service;

import com.alibaba.fastjson.JSONObject;

import com.alibaba.nacos.api.NacosFactory;

import com.alibaba.nacos.api.config.ConfigService;

import com.alibaba.nacos.api.config.listener.Listener;

import com.alibaba.nacos.api.exception.NacosException;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.cloud.gateway.event.RefreshRoutesEvent;

import org.springframework.cloud.gateway.route.RouteDefinition;

import org.springframework.cloud.gateway.route.RouteDefinitionWriter;

import org.springframework.context.ApplicationEventPublisher;

import org.springframework.context.ApplicationEventPublisherAware;

import org.springframework.stereotype.Component;

import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.Executor;

@Component

public class NacosDynamicRouteService implements ApplicationEventPublisherAware {

private String dataId = "gateway-router";

private String group = "DEFAULT_GROUP";

@Value("${spring.cloud.nacos.config.server-addr}")

private String serverAddr;

@Autowired

private RouteDefinitionWriter routeDefinitionWriter;

private ApplicationEventPublisher applicationEventPublisher;

private static final List<String> ROUTE_LIST = new ArrayList<>();

@PostConstruct

public void dynamicRouteByNacosListener() {

    try {

        ConfigService configService = NacosFactory.createConfigService(serverAddr);

        configService.getConfig(dataId, group, 5000);

        configService.addListener(dataId, group, new Listener() {

            @Override

            public void receiveConfigInfo(String configInfo) {

                clearRoute();

                try {

                    List<RouteDefinition> gatewayRouteDefinitions = JSONObject.

parseArray(configInfo, RouteDefinition.class);

                    for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {

                        addRoute(routeDefinition);

                    }

                    publish();

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

            @Override

            public Executor getExecutor() {

                return null;

            }

        });

    } catch (NacosException e) {

        e.printStackTrace();

    }

}

private void clearRoute() {

    for(String id : ROUTE_LIST) {

        this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();

    }

    ROUTE_LIST.clear();

}

private void addRoute(RouteDefinition definition) {

    try {

        routeDefinitionWriter.save(Mono.just(definition)).subscribe();

        ROUTE_LIST.add(definition.getId());

    } catch (Exception e) {

        e.printStackTrace();

    }

}

private void publish() {

    this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));

}

@Override

public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {

    this.applicationEventPublisher = applicationEventPublisher;

}

}

|

2.7.3.2 创建nacos配置

代码中监听的配置ID为gateway-router,按此ID在Nacos中创建配置

[图片上传失败...(image-6baa7b-1593312061456)]

2.7.3.3 注释bootstrap.yml中的路由配置

从bootstrap.yml中删除路由配置,即删除以下内容

routes:

  - id: payment-router

    uri: lb://payment-service

    predicates:

    - Path=/pay/**

2.7.3.4 添加动态路由设置

动态路由功能修改完成,启动gateway测试,目前路由表中仅匹配了/acc/**的,分别测试一下/acc和/pay

[图片上传失败...(image-d7be3b-1593312061464)]

[图片上传失败...(image-36f15f-1593312061464)]

/acc/user请求成功转发到account-service,而/pay/balance没有找到匹配的路由信息,与期望行为一致。

下面来动态增加/pay的路由,修改Nacos中的gateway-router配置如下:

[{

"id": "account-router",

"order": 0,

"predicates": [{

    "args": {

        "pattern": "/acc/**"

    },

    "name": "Path"

}],

"uri": "[lb://account-service](lb://account-service)"

},{

"id": "payment-router",

"order": 2,

"predicates": [{

    "args": {

        "pattern": "/pay/**"

    },

    "name": "Path"

}],

"uri": "[lb://payment-service](lb://payment-service)"

}]

不重启gateway再次测试/pay/balance请求 ,已经可以达到访问预期效果。

[图片上传失败...(image-78f185-1593312061463)]

2.8 在ecology整合二次开发微服务

2.8.1 说明

原则上所有的ecology和微服务的交互都是通过RestApi

2.8.2 微服务配置平台部署

2.8.2.1 部署微服务配置平台

  1. 将“微服务配置平台后端.zip”解压覆盖到ecology目录(如果已经部署过”wn_E9Mybatis.jar”,请使用“微服务配置平台(不含Mybatis.jar)”版本)。

  2. 将\WEB-INF\config\mapper\weavernorth\MicoService\MicoService.xml注册到WN_MyBatisConfig.xml中

[图片上传失败...(image-996017-1593312061463)]

  1. 重启服务器。

  2. 访问ecology->后端应用中心->微服务配置平台->微服务网关注册平台,如果出现以下页面代表部署成功

[图片上传失败...(image-727e16-1593312061463)]

2.8.2.2 使用微服务配置平台

2.8.2.2.1 注册网关

在“微服务网关注册平台”中点击编辑,设置ServiceID、URL、是否启用,URL填写SpringCloudGetWay网关地址或springboot单体应用地址都可以,ServiceID保证唯一。

[图片上传失败...(image-4bde0d-1593312061463)]

2.8.2.2.2 在Action中代码示例

|

import com.alibaba.fastjson.JSONObject;

import com.weavernorth.Developer.MicoServiceUtil;

import weaver.soa.workflow.request.RequestInfo;

import weaver.workflow.action.BaseAction;

public class SalaryPaymentAction extends BaseAction {

@Override

public String execute(RequestInfo requestInfo) {

    //获取微服务网关地址

    String strMicoServiceURI= MicoServiceUtil.getMicoServiceURI("Salary");

    //获取Requestid

    String strRequestid=requestInfo.getRequestid();

    //调用微服务

    JSONObject returnjson=MicoServiceUtil.httpGet(strMicoServiceURI+"/api/weavernorth/hrm/computeSalary?strRequestid="+strRequestid);

    if(returnjson.get("flag").equals("success")){

        return SUCCESS;

    }else{

        requestInfo.getRequestManager().setMessage(returnjson.get("message").toString());

        return "0";

    }

}

}

|

2.8.2.2.3 在JavaScript中代码示例

<script type="text/javascript" src="/weavernorth/developer/MicoServiceUtil.js"></script>

<script>

//微服务地址

var MicoServiceURI="";

$(function(){

MicoServiceURI=getMicoServiceURI("Salary");

})

$.ajax({

        type:"get",

        url:MicoServiceURI+"/api/weavernorth/hrm/getSalaryReissue,

        contentType:"application/json",

        dataType:"json",

        success:function(jsonarray){

            console.log(jsonarray)

        }

    })

<script>

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