十次方后端笔记一:系统设计与标签管理模块
十次方项目搭建、系统介绍和基础微服务搭建

《十次方》是程序员的专属社交平台,包括头条、问答、活动、交友、吐槽、招聘六大
频道。

系统设计

《十次方》采用前后端分离的系统架构,后端架构为:
SpringBoot+SpringCloud+SpringMVC+SpringData 我们把这种架构也称之为全家桶。

模块划分

十次方工程共分为18个子模块(其中17个是微服务)

模块名称 模块中文名称
tensquare_common 公共模块
tensquare_article 文章微服务
tensquare_base 基础微服务
tensquare_friend 交友微服务
tensquare_gathering 活动微服务
tensquare_qa 问答微服务
tensquare_recruit 招聘微服务
tensquare_user 用户微服务
tensquare_spit 吐槽微服务
tensquare_search 搜索微服务
tensquare_web 前台微服务网关
tensquare_manager 后台微服务网关
tensquare_eureka 注册中心
tensquare_config 配置中心
tensquare_sms 短信微服务
tensquare_article_crawler 文章爬虫微服务
tensquare_user_crawler 用户爬虫微服务
tensquare_ai 人工智能微服务

后端状态码定义

状态描述 返回码
成功 20000
失败 20001
用户名密码错误 20002
权限不足 20003
远程调用失败 20004
重复操作 20005

项目准备

VMware Workstation Pro

CentOS 7镜像

安装CentOS 7(省略)

CentOS 7安装docker

安装docker需要保证你的CentOS 7能够连接到互联网,可以在虚拟机中使用ping命令。

1
ping baidu.com
  1. 通过 uname -r 命令查看你当前的内核版本

    1
    uname -r # 大于等于3.10即可
  2. 移除旧的版本docker

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    yum remove docker \
    docker-client \
    docker-client-latest \
    docker-common \
    docker-latest \
    docker-latest-logrotate \
    docker-logrotate \
    docker-selinux \
    docker-engine-selinux \
    docker-engine
  3. 安装依赖

    1
    yum install -y yum-utils device-mapper-persistent-data lvm2
  4. 增加yum

    1
    yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  5. 更新yum缓存

    1
    yum makecache fast
  6. 安装 Docker-cece为社区版,ee为企业版,但是要收费🤦‍

    1
    yum -y install docker-ce
  7. 启动 Docker 后台服务

    1
    systemctl start docker
  8. 查看是否安装成功

    1
    docker -version

安装MySQL镜像

  1. 搜索mysql镜像

    1
    docker search mysql

  2. 拉取mysql镜像

    1
    2
    3
    # 这里拉取centos提供的mysql镜像,这个过程可能会非常的缓慢
    # 实在不行就用人家提供好的centos镜像吧
    docker pull centos/mysql‐57‐centos7

    CentOS提供的镜像集合:docker hub

    会介绍每一个容器的使用方法,建议浏览,但是是英文的(右键 -> 谷歌翻译 o( ̄▽ ̄)d)。

  3. 运行mysql镜像

    1
    2
    3
    4
    # 查看本地镜像
    docker images
    # 安装mysql镜像
    docker run ‐di ‐‐name=tensquare_mysql ‐p 3306:3306 ‐e MYSQL_ROOT_PASSWORD=123456  centos/mysql‐57‐centos7
  4. 使用数据库界面工具连接mysql

建库建表(省略)

项目搭建

父工程搭建

创建module

  • GroupId:com.tensquare
  • ArtifactId:tensquare_parent

pom.xml依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.tensquare</groupId>
<artifactId>tensquare_parent</artifactId>
<packaging>pom</packaging>
<version>1.0.0-SNAPSHOT</version>

<description>十次方项目</description>
<modules>
<module>tensquare_common</module>
</modules>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<tensquare.version>1.0.0-SNAPSHOT</tensquare.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>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

公共模块搭建

创建module

  • GroupId:com.tensquare
  • ArtifactId:tensquare_common

实体类

  1. 通用返回结果实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    package entity;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    /**
    * 统一返回结果对象
    */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Result {

    /**
    * 状态标识
    */
    private boolean flag;

    /**
    * 状态码
    */
    private Integer code;

    /**
    * 提示消息
    */
    private String message;

    /**
    * 数据
    */
    private Object data;

    public Result(boolean flag, Integer code, String message) {
    this.flag = flag;
    this.code = code;
    this.message = message;
    }

    }
  2. 状态码枚举类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package entity;

    import lombok.Getter;

    /**
    * 状态码枚举类
    */
    @Getter
    public enum StatusCode {

    OK(20000, "请求成功"),
    ERROR(20001, "请求失败"),
    LOGIN_ERROR(20002, "用户名或密码错误"),
    ACCESS_ERROR(20003, "权限不足"),
    REMOTE_ERROR(20004, "远程调用失败"),
    REP_ERROR(20005, "重复操作"),
    ;

    private int statusCode;
    private String defaultMessage;

    StatusCode(int statusCode, String defaultMessage) {
    this.statusCode = statusCode;
    this.defaultMessage = defaultMessage;
    }
    }
  3. 分页结果实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package entity;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import java.util.List;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class PageResult<T> {

    private Long total;
    private List<T> rows;

    }

ID生成工具类

由于我们的数据库在生产环境中要分片部署(MyCat),所以我们不能使用数据库本身的自增功能来产生主键值,只能由程序来生成唯一的主键值。

我们采用的是开源的twitter( 非官方中文惯称:推特.是国外的一个网站,是一个社交网络及微博客服务) 的snowflake(雪花)算法。

基础微服务(tensquare_base)

标签管理(CRUD)

表结构分析

字段名称 字段含义 字段类型 备注
id ID 文本
labelname 标签名称 文本
state 状态 文本 0:无效 1:有效
count 使用数量 整型
fans 关注数 整型
recommend 是否推荐 文本 0:不推荐1:推荐

准备工作

  1. 项目搭建

  2. pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <?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/xsd/maven-4.0.0.xsd">
    <parent>
    <artifactId>tensquare_parent</artifactId>
    <groupId>com.tensquare</groupId>
    <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>tensquare_base</artifactId>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
    <groupId>com.tensquare</groupId>
    <artifactId>tensquare_common</artifactId>
    <version>${tensquare.version}</version>
    </dependency>
    </dependencies>

    </project>
  3. 启动类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.tensquare;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class BaseApplication {
    public static void main(String[] args) {
    SpringApplication.run(BaseApplication.class, args);
    }
    }
  4. application.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    server:
    port: 9001
    spring:
    application:
    name: tensquare-base
    datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.136.104:3306/tensquare_base?characterEncoding=utf8
    username: root
    password: 123456
    jpa:
    database: MySQL
    show‐sql: true
    generate‐ddl: true

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.tensquare.base.entity;

import constants.ModelConstants;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = ModelConstants.TABLE_LABEL)
public class Label {

@Id
private String id;

/**
* 标签名称
*/
private String labelname;

/**
* 标签状态, 0:无效 1:有效
*/
private String state;

/**
* 使用数量
*/
private Long count;

/**
* 是否推荐, 0:不推荐 1:推荐
*/
private String recommend;

/**
* 关注数
*/
private Long fans;
}

这里我正在tensquare中新建了ModelConstants专门存放实体相关的一些常量,比如表名称,字段名称等

ModelConstants如下:

1
2
3
4
5
6
7
8
9
10
11
package constants;

/**
* 模型常量类
*/
public class ModelConstants {
/**
* 表名称常量
*/
public static final String TABLE_LABEL = "tb_label";
}

config

  1. IdWorkerProperties

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.tensquare.base.config;

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;

    @Data
    @ConfigurationProperties("tensquare.worker")
    public class IdWorkerProperties {

    /**
    * 节点ID
    */
    private Long workerId;

    /**
    * 机器ID
    */
    private Long datacenterId;

    }

    需要在application.yaml中配置:

    1
    2
    3
    4
    tensquare:
    worker:
    workerId: 1
    datacenterId: 1
  2. IdWorkerConfiguration

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.tensquare.base.config;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import util.IdWorker;

    @Configuration
    @EnableConfigurationProperties(IdWorkerProperties.class)
    public class IdWorkerConfiguration {

    @Autowired
    private IdWorkerProperties prop;


    @Bean
    public IdWorker getIdWorker() {
    return new IdWorker(prop.getWorkerId(), prop.getDatacenterId());
    }

    }

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.tensquare.base.controller;

import com.tensquare.base.entity.Label;
import com.tensquare.base.service.LabelService;
import entity.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("label")
public class LabelController {

@Autowired
private LabelService labelService;

@PostMapping
public Result insert(@RequestBody Label label) {
labelService.save(label);
return Result.ok("新增成功");
}

@GetMapping
public Result queryLabels() {
List<Label> labelList = labelService.findAll();
return Result.ok(labelList);
}

@GetMapping("{labelId}")
public Result queryById(@PathVariable("labelId") String id) {
return Result.ok(labelService.findById(id));
}

@DeleteMapping("{labelId}")
public Result remove(@PathVariable("labelId") String id) {
labelService.deleteById(id);
return Result.ok("删除成功");
}

@PutMapping("{labelId}")
public Result update(@RequestBody Label label,
@PathVariable("labelId") String id) {
label.setId(id);
labelService.update(label);
return Result.ok("修改成功");
}

@PostMapping("/search/{page}/{size}")
public Result queryByPage(@RequestBody Map searchMap,
@PathVariable("page") int page,
@PathVariable("size") int size) {
return Result.ok(labelService.queryByPage(searchMap, page, size));
}


}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.tensquare.base.service;

import com.tensquare.base.dao.LabelRepository;
import com.tensquare.base.entity.Label;
import entity.PageResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import util.IdWorker;

import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class LabelService {

@Autowired
private LabelRepository labelRepository;

@Autowired
private IdWorker idWorker;

public Label save(Label label) {
label.setId(idWorker.nextId().toString());
return labelRepository.save(label);
}

public List<Label> findAll() {
return labelRepository.findAll();
}

public Label findById(String id) {
return labelRepository.findById(id).get();
}

public void deleteById(String id) {
labelRepository.deleteById(id);
}

public void update(Label label) {
labelRepository.save(label);
}

/**
* 分页+条件
*/
public PageResult<Label> queryByPage(Map searchMap, int page, int size) {
Specification<Label> specification = createSpecification(searchMap);
Page<Label> labelPage = labelRepository.findAll(specification, PageRequest.of(page, size));
return new PageResult<>(labelPage.getTotalElements(), labelPage.getContent());
}

public List<Label> findAll(Map searchMap) {
Specification<Label> specification = createSpecification(searchMap);
return labelRepository.findAll(specification);
}

/**
* 构建查询条件
*
* @param searchMap 查询条件
* @return Specification 查询规格
*/
private Specification<Label> createSpecification(Map searchMap) {
String labelname = (String) searchMap.get("labelname");
String state = (String) searchMap.get("state");
String recommend = (String) searchMap.get("recommend");
return (Specification<Label>) (root, criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicateList = new ArrayList<>();

if (!StringUtils.isEmpty(labelname)) {
predicateList.add(criteriaBuilder.like(root.get("labelname").as(String.class), "%" + labelname + "%"));
}
if (!StringUtils.isEmpty(state)) {
predicateList.add(criteriaBuilder.equal(root.get("state").as(String.class), state));
}
if (!StringUtils.isEmpty(recommend)) {
predicateList.add(criteriaBuilder.equal(root.get("recommend").as(String.class), "%" + recommend + "%"));
}
return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));
};
}
}

dao

1
2
3
4
5
6
7
8
package com.tensquare.base.dao;

import com.tensquare.base.entity.Label;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface LabelRepository extends JpaRepository<Label, String>, JpaSpecificationExecutor<Label> {
}

统一异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.tensquare.base.exception;

import entity.Result;
import entity.StatusCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class BaseExceptionHandler {

@ExceptionHandler(Exception.class)
public Result error(Exception e) {
log.error("[发生异常] ", e);
return Result.error(StatusCode.ERROR.getStatusCode(), StatusCode.ERROR.getDefaultMessage());
}

}
文章作者: imxushuai
文章链接: https://www.imxushuai.com/2002/01/02/1.十次方后端笔记一:系统设计与标签管理模块/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 imxushuai
支付宝打赏
微信打赏