1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/happydts-seckill

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

В запросе скорее всего текст технической направленности из области разработки и тестирования программного обеспечения. Основной язык текста запроса — китайский.

  1. 输入 auth leyou,显示 OK,则密码正确。
  2. 实际测试一下读写。输入 set mykey «abd» 并回车,用来保存一个键值。再输入 get mykey,获取刚才保存的键值。
  3. 开启持久化,appendonly yes — 默认为 no。
  4. 持久化:将数据(如内存中的对象)保存到可永久保存的存储设备中。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、 XML 数据文件中等等。
  5. Redis 的数据都是缓存在内存中,当你重启系统或者关闭系统后,缓存在内存中的数据都会全部消失,再也找不回来了。所以为了让数据能够长期保存,就要将 Redis 放在缓存中的数据做持久化存储。
  6. 写入时机默认为 everysec,每秒。也可以设置为 always,实时写入,但是会有效率问题。
  7. 900 秒有一个值存入,就持久化一次。
  8. 300 秒有 10 个值存入,就持久化一次。
  9. 60 秒有 10000 个值存入,就持久化一次.

3.2. Установка RabbitMQ и конфигурация

3.2.1. Установка RabbitMQ клиента

Установить otp_win64_20.2.exe.

3.2.2. Установка RabbitMQ сервера

Установить rabbitmq-server-3.7.4.exe.

3.2.3. Конфигурация RabbitMQ сервера

Настроить RabbitMQ клиентскую среду.

Настроить RabbitMQ серверную среду.

В среде переменных добавить RabbitMQ сервер.

Установить плагин: rabbitmq-plugins.bat enable rabbitmq_management. Перезапустить RabbitMQ сервис. Запустить RabbitMQ.

四、秒杀系统创建

Сначала посмотрим на структуру каталога секундного убийства.

4.1. Создание Eureka регистрационного центра (порт 9000)

Шаг 1: Выберите File-New-Module... В появившемся окне выберите Spring initializr, выберите Module SDK, выберите Next.

Шаг 2: Group для com.itheima, Artifact для leyou, Name для leyouServer, Description для Server For leyou Project, выберите Next.

Шаг 3: Выберите Spring Cloud Discovery, выберите Eureka Server, выберите Next.

Шаг 4: Module name — leyouServer, Content root — путь + leyouServer, выберите Finish, в этот момент в папке проекта будет создана папка leyouServer.

4.1.1. Конфигурирование pom.xml

Откройте pom.xml в сгенерированном проекте и настройте зависимости.

<?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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <groupId>com.itheima</groupId>
    <artifactId>leyou</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

Импортируйте изменения в Maven. В правом нижнем углу всплывающего окна нажмите Import Changes, Maven автоматически загрузит необходимые jar-файлы из удалённого репозитория. Если импорт не удался, вы можете следовать инструкциям ниже:

  • В IDEA в разделе Maven Projects выберите leyouServer и нажмите правой кнопкой мыши на Dependencies, затем выберите Download Sources. Maven также автоматически загрузит нужные jar-файлы из удалённого репозитория. Все последующие проекты должны быть настроены таким же образом.

4.1.2. Настройка файла application.properties

Откройте src\man\resources\application.properties в сгенерированном проекте и настройте порт и другие параметры.

server.port=9000
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
eureka.instance.hostname=localhost
spring.application.name=leyou-server
# 不从服务器拿服务信息
eureka.client.fetch-registry=false
# 不在服务端注册
eureka.client.register-with-eureka=false
``` ### Тестирование программного обеспечения

1. **Тестовый запуск сервиса товаров.** В регистрационном центре Eureka можно увидеть сервис leyou-Stock, что свидетельствует об успешном запуске сервиса.

2. **Создание папок controller, service и dao, а также файлов StockController.java, StockService.java и StockDao.java.**
    * Controller отвечает за управление бизнес-процессами (Service) и управление переходами.
    * Service отвечает за конкретные функции и ветвление решений.
    * Dao отвечает за взаимодействие с данными и выполнение операций добавления, изменения, удаления и запроса данных.

3. **Контроллер похож на официанта**, который принимает заказ от клиента и направляет его к определённому столику.

4. **Сервис похож на повара**, который готовит блюда из меню, полученного от клиента.

5. **Dao похож на кухонного работника**, который работает с исходными материалами.

6. **Создание метода для получения списка товаров (getStockList).**
   * Метод используется для предоставления данных о товарах на страницу переднего плана.
   * Сначала необходимо определить структуру данных для обмена с передним планом:
     * Если возвращаемое значение неверно, то {"result":"false", "msg":"****"}.
     * Если возвращаемое значение верно, то {"result":"true", "msg":"","sku_list":["id":1,"sku_id":...]}.
   * Затем нужно создать интерфейс IStockDao и реализовать его в классе StockDao.
   * Далее следует добавить аннотацию @Repository для обозначения компонента доступа к данным (DAO).
   * Необходимо объявить метод JDBCTemplate для подключения к базе данных.
   * Нужно создать метод getStockList, который будет получать данные о товарах из базы данных и помещать их в список ArrayList. Затем этот список будет возвращён.

7. **Затем нужно написать код для сервиса.**
   * Создаётся интерфейс IStockService.
   * Класс StockService реализует интерфейс IStockService и получает аннотацию @Service.
   * В класс StockService добавляется ссылка на интерфейс IStockDao.
   * Добавляется метод getStockList, который вызывает метод getStockList из класса StockDao и возвращает карту Map.

8. **Создаётся метод getStock для класса StockDao.** Этот метод принимает параметр sku_id и выполняет запрос к базе данных, чтобы получить информацию о товаре по этому идентификатору.

9. **В классе StockService создаётся метод getStock,** который возвращает карту с информацией о товаре.

10. **Для тестирования метода getStockList** используется URL http://localhost:7000/getStockList. Вот перевод текста на русский язык:

policyMap){ //1、判断传入的参数是不是合法 Map<String, Object> resultMap = new HashMap<String, Object>(); if (policyMap==null||policyMap.isEmpty()){ resultMap.put("result", false); resultMap.put("msg", "传入什么东东"); return resultMap; }

//2、从StockDao接口中调用insertLimitPolicy方法
boolean result = iStockDao.insertLimitPolicy(policyMap);

//3、判断执行成功或失败,如果失败,返回错误信息
if (!result){
    resultMap.put("result", false);
    resultMap.put("msg", "数据执行咋又失败了");
    return resultMap;
}

//4、如果成功,写入redis,需要写入有效期,key取名:LIMIT_POLICY_{sku_id}
long diff = 0;
String now = restTemplate.getForObject("http://leyou-time-server/getTime", String.class);

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
//结束日期减去当前日期得到政策的有效期
try {
    Date end_time = simpleDateFormat.parse(policyMap.get("end_time").toString());
    Date now_time = simpleDateFormat.parse(now);
    diff = (end_time.getTime() - now_time.getTime())/1000;

    if (diff<0){
        resultMap.put("result", false);
        resultMap.put("msg", "结束时间不能小于当前时间");
        return resultMap;
    }
} catch (ParseException e) {
    resultMap.put("result", false);
    resultMap.put("msg", "日期转换又失败了");
    return resultMap;
}

String policy = JSON.toJSONString(policyMap);
stringRedisTemplate.opsForValue().set("LIMIT_POLICY_"+policyMap.get("sku_id").toString(), policy, diff, TimeUnit.SECONDS);
ArrayList<Map<String, Object>> list = iStockDao.getStock(policyMap.get("sku_id").toString());
String sku = JSON.toJSONString(list.get(0));
stringRedisTemplate.opsForValue().set("SKU_"+policyMap.get("sku_id").toString(), sku, diff, TimeUnit.SECONDS);

//5、返回正常信息
resultMap.put("result", true);
resultMap.put("msg", "");
return resultMap;

}


**注意:这里的Service有业务逻辑判断,首先判断传入的json是否可以转化成功,其次是写入数据库成功或者失败。**

**Здесь добавлен @Transactional, который требует открытия транзакции. Необходимо помнить, что только три оператора insert\delete\update требуют открытия транзакции, и если запись в базу данных не удалась, можно откатить. Поскольку вышеописанные методы являются запросами, нет необходимости открывать транзакцию.**

В StockController добавлен метод insertLimitPolicy, требующий добавления аннотации @RequestMapping, где value = "/insertLimitPolicy/{jsonObj}", возвращающий товар Map, код следующий:

```java
@RequestMapping(value = "/insertLimitPolicy/{jsonObj}")
public Map<String, Object> insertLimitPolicy(@PathVariable("jsonObj") String jsonObj){
    return iStockService.insertLimitPolicy(jsonObj);
}

Для тестирования метода insertLimitPolicy введите в браузере http://localhost:7000/insertLimitPolicy/{sku_id:'26816294479',quanty:1000,price:1000,begin_time:'2019-08-05 11:00',end_time:'2019-10-05 12:00'}.

4.3.11. Ввод политики в Redis при добавлении новой политики

Шаги:

  1. Настройка зависимостей Redis.
  2. Добавление конфигурации Redis в файл application.properties.
  3. Написание класса запуска с добавлением RestTemplate и StringRedisTemplate.
  4. Добавление переменных объявления RestTemplate и StringRedisTemplate в Service.
  5. Запись в таблицу политик базы данных.
  6. Запись политики в Redis с ключом LIMIT_POLICY_{sku_id}.
  7. Когда время окончания политики истекает, используйте механизм удаления Redis для автоматического удаления, чтобы уменьшить использование памяти. Время получается путём вычитания текущего времени из времени окончания политики.

Код следующий:

Объявление StringRestTemplate:

@Autowired
StringRedisTemplate stringRedisTemplate;

Изменение метода insertLimitPolicy в StockService с добавлением следующего кода:

4.3.12. Упаковка методов получения политики Redis при упаковке списка товаров и деталей товаров (getLimitPolicy)

Шаги:

  1. Пройтись по списку товаров.
  2. Получить политику каждого sku_id.
  3. Присвоить значение списку товаров.

Метод упаковки: getLimitPolicy

Код следующий:

private Map<String, Object> getLimitPolicy(ArrayList<Map<String, Object>> list){
    Map<String, Object> resultMap = new HashMap<String, Object>();

    for (Map<String, Object> skuMap: list){
        //3.1、Извлечь политику из redis
        String policy = stringRedisTemplate.opsForValue().get("LIMIT_POLICY_"+skuMap.get("sku_id").toString());

        //3.2、Только если есть политика, продолжить
        if (policy!=null&&!policy.equals("")){
            Map<String, Object> policyInfo = JSONObject.parseObject(policy, Map.class);

            //3.3、Если начало времени меньше или равно текущему времени, а текущее время меньше или равно концу времени

``` Вот перевод текста на русский язык:

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); String now = restTemplate.getForObject("http://leyou-time-server/getTime", String.class); try { Date end_time = simpleDateFormat.parse(policyInfo.get("end_time").toString()); Date begin_time = simpleDateFormat.parse(policyInfo.get("begin_time").toString()); Date now_time = simpleDateFormat.parse(now);

if (begin_time.getTime() <= now_time.getTime() && now_time.getTime() <= end_time.getTime()) {
    skuMap.put("limitPrice", policyInfo.get("price"));
    skuMap.put("limitQuanty", policyInfo.get("quanty"));
    skuMap.put("limitBeginTime", policyInfo.get("begin_time"));
    skuMap.put("limitEndTime", policyInfo.get("end_time"));
    skuMap.put("nowTime", now);
}

} catch (ParseException e) { e.printStackTrace(); } } resultMap.put("result", true); resultMap.put("msg", ""); return resultMap; }


Здесь нужно обратить внимание: политика записывается в список и передаётся на переднюю страницу.

* limitPrice: цена политики.
* limitBeginTime: время начала политики.
* limitEndTime: время окончания политики.
* nowTime: текущее время.

## 4.4 Создание сервиса для управления запасами (порт номер 6001)

------

**Обратите внимание: здесь конфигурация порта номер не 6000, а 6001, причина в том, что порт 6000 недоступен в браузере Chrome Google.**

### 4.4.1 Создание структуры таблицы запасов

1. Таблица склада:

```sql
DROP TABLE IF EXISTS `tb_warehouse`;
CREATE TABLE `tb_warehouse` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'идентификатор склада',
  `name` VARCHAR(64) NOT NULL COMMENT 'название склада',
  `create_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'время создания',
  `update_time` TIMESTAMP NULL COMMENT 'время обновления',
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
  1. Основная таблица запасов:

Назначение: используется для хранения остатков запасов.

DROP TABLE IF EXISTS `tb_stock_storage`;
CREATE TABLE `tb_stock_storage` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `warehouse_id` BIGINT(20) NOT NULL COMMENT 'идентификатор склада',
  `sku_id` BIGINT(20) NOT NULL COMMENT 'sku id',
  `quanty` DECIMAL(18,2) COMMENT 'остаток количества',
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
  1. Таблица истории запасов:

Назначение: используется для записи истории движения запасов.

DROP TABLE IF EXISTS `tb_stock_storage_history`;
CREATE TABLE `tb_stock_storage_history` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `stock_storage_id` BIGINT(20) NOT NULL COMMENT 'идентификатор основной таблицы запасов',
  `in_quanty` DECIMAL(18,2) COMMENT 'количество поступления',
  `out_quanty` DECIMAL(18,2) COMMENT 'количество выбытия',
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

Правила записи запасов:

tb_stock_storage.png

Примечание: в основной таблице запасов один склад соответствует одному товару, и хранится только одна запись, в которой quanty хранит остаток количества.

В таблице истории запасов каждый склад имеет соответствующую основную таблицу запасов, которая записывает историю движения количества каждого товара.

Объяснение правил записи:

  1. Определить наличие данных в основной таблице запасов по sku_id;

  2. Если есть данные, получить идентификатор основной таблицы запасов;

  3. Если нет данных, сначала записать в основную таблицу запасов и получить идентификатор основной таблицы запасов;

  4. Записать в таблицу истории запасов на основе идентификатора основной таблицы запасов;

  5. Когда в основной таблице запасов есть данные, обновить количество на основе идентификатора основной таблицы запасов.

4.4.2 Создание сервиса управления запасами

Первый шаг: выберите File-New-Module..., появится окно, выберите Spring initializr, выберите Module SDK, выберите Next

EurekaServer.png

Второй шаг: Group — com.itheima, Artifact — leyou, Name — leyouStorage, Description — Storage For leyou Project, выберите Next

eurekaStorage1.png

Третий шаг: Выберите Spring Cloud Discovery, выберите Eureka Discovery Client, выберите Next

leyouStock3.png

Четвёртый шаг: Module name — leyouStorage, Content root — путь + leyouStorage, выберите Finish, после чего в папке проекта будет создана папка leyouStorage.

eurekaStorage2.png

4.4.3 Конфигурация pom.xml

В созданном проекте откройте pom.xml, настройте зависимости, где spring-cloud-starter-netflix-eureka-client — это jar-пакет, который вводит клиент Eureka в проект, spring-boot-starter-web — это jar-пакеты, используемые при разработке веб-модулей, alibaba.fastjson — это jar-пакеты, которые используют fastjson от Alibaba для анализа данных json, mysql-connector-java и spring-boot-starter-data-jpa — это зависимости, которые вводят jar-пакеты для подключения к MySQL. Конфигурационный файл application.properties

В созданном проекте откройте файл src\main\resources\application.properties и настройте номер порта и другие параметры:

server.port=6001
spring.application.name=leyou-storage
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/code1_2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

Добавление аннотации @EnableEurekaClient в класс StorageApplication

В созданном проекте откройте файл src\main\java\com.itheima.leyou\leyouStorageApplication. В уже существующий класс StorageApplication добавьте аннотацию @EnableEurekaClient, чтобы запустить Eureka клиент:

@SpringBootApplication
@EnableEurekaClient
public class StorageApplication {
   public static void main(String[] args) {
      SpringApplication.run(StorageApplication.class, args);
   }
}

Создание папок controller, service и dao, а также файлов StorageController.java, StorageService.java и StorageDao.java

Создайте интерфейс IStorageDao:

public interface IStorageDao {}

Добавьте аннотацию @Repository к классу StorageDao, чтобы пометить его как компонент доступа к данным (DAO):

@Repository
public class StorageDao implements IStorageDao{}

Объявите метод JDBCTemplate для подключения к базе данных:

@Autowired
private JdbcTemplate jdbcTemplate;

Создайте интерфейс IStorageService:

@Service
public class StorageService implements IStorageService {}

Внедрите интерфейс IStorageDao в класс StorageService:

@Autowired
private IStorageDao iStorageDao;

Добавьте аннотации к классу StorageController:

@RestController
@Configuration
public class StorageController {}

Внедрите интерфейс IStorageService в класс StorageController:

@Autowired
private IStorageService iStorageService;

Метод getStockStorage для запроса фактического количества товаров на складе

Метод getStockStorage в классе StorageDao принимает параметр sku_id и возвращает список товаров:

public ArrayList<Map<String, Object>> getStockStorage(String sku_id){
    //1、SQL запрос
    String sql = "SELECT sku_id, quanty FROM tb_stock_storage WHERE sku_id = ?";

    //2、Возвращаем данные
    return (ArrayList<Map<String,Object>>) jdbcTemplate.queryForList(sql, sku_id);
}

Метод getStockStorage в классе StorageService возвращает карту товаров:

public Map<String, Object> getStockStorage(String sku_id){
    //1, Получаем количество товара на складе
    ArrayList<Map<String ,Object>>
``` **Версия 1.0**

Кодировка UTF-8

Проект Maven

```xml
<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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <groupId>com.itheima</groupId>
    <artifactId>leyou</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>leyouUser</name>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>2.1.7.RELEASE</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

Конфигурация файла application.properties

В сгенерированном проекте откройте src\main\resources\application.properties, настройте номер порта и другие параметры.

server.port=5000
spring.application.name=leyou-user
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/code1_2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

#redis数据库编号,существует 0~15, всего 16 баз данных
spring.redis.database=0
#redis сервер IP
spring.redis.host=127.0.0.1
#redis порт
spring.redis.port=6379
#redis пароль
spring.redis.password=leyou
#redis запрос тайм-аут, превышающий это значение, redis автоматически отключит соединение
spring.redis.timeout=10000ms
#jedis максимальное количество подключений, превышение этого значения вызовет исключение «невозможно получить подключение»
spring.redis.jedis.pool.max-active=32
#jedis максимальный период ожидания, превышение которого вызовет исключение «тайм-аут подключения»
spring.redis.jedis.pool.max-wait=10000ms
#jedis максимальное время простоя соединения
spring.redis.jedis.pool.max-idle=32
#jedis минимальное время простоя соединения
spring.redis.jedis.pool.min-idle=0

В сгенерированном проекте откройте файл src\main\java\com.itheima.leyou\leyouUserApplication и добавьте аннотацию @EnableEurekaClient в уже созданный класс запуска UserApplication. Это означает запуск Eureka Client.

@SpringBootApplication
@EnableEurekaClient
public class UserApplication {
   public static void main(String[] args) {
      SpringApplication.run(UserApplication.class, args);
   }
}

Протестируйте запуск службы заказов. В центре регистрации Eureka можно увидеть службу leyou-User, что свидетельствует об успешном запуске службы.

Создание папок controller, service и dao, а также файлов UserController.java, UserService.java и UserDao.java

Создайте интерфейс Dao слоя IUserDao.

public interface IUserDao {}

Добавьте аннотации к классу UserDao.java для реализации интерфейса IUserDao и использования @Repository.

@Repository
public class UserDao implements IUserDao {}

Объявите метод JDBCTemplate для подключения к базе данных.

@Autowired
private JdbcTemplate jdbcTemplate;

Создайте сервисный слой интерфейса IUserService.

Добавьте аннотацию к классу UserService.java для реализации IUserService интерфейса. Реализует IUserService{}

Присоединить ссылку на UserDao

```java
@Autowired
private IUserDao iUserDao;

Добавить аннотацию к классу UserController.java

@RestController
public class UserController {}

Присоединить ссылку на интерфейс IUserService

@Autowired
private IUserService iUserService;

4.5.6. Создание метода запроса члена (getUser)

Назначение: предоставить данные информации о члене для внешнего интерфейса, в основном используется для входа члена во внешний интерфейс

В классе UserDao.java добавить метод getUser с двумя параметрами username и password, что означает поиск по имени пользователя и паролю и возврат списка членов. Код выглядит следующим образом:

public ArrayList<Map<String, Object>> getUser(String username, String password){  
    String sql = "select id AS user_id, username, phone, password from tb_user where username = ?";  
    return (ArrayList<Map<String,Object>>) jdbcTemplate.queryForList(sql, username);  
}  

В классе UserService.java добавить метод getUser, который возвращает список товаров. Код выглядит следующим образом:

public Map<String, Object> getUser(String username, String password){  
    Map<String, Object> resultMap = new HashMap<String, Object>();  
    //1、Проверить правильность переданных параметров  
    if (username==null||username.equals("")){  
        resultMap.put("result", false);  
        resultMap.put("msg", "Имя пользователя не может быть пустым!");  
        return resultMap;  
    }  

    //2、Получить список членов  
    ArrayList<Map<String, Object>> list = iUserDao.getUser(username, password);  
    if (list==null||list.isEmpty()){  
        resultMap.put("result", false);  
        resultMap.put("msg", "Информация о члене не найдена!");  
        return resultMap;  
    }  

    resultMap = list.get(0);  
    resultMap.put("result", true);  
    resultMap.put("msg", "");  
    return resultMap;  
}  

4.5.7. Создание метода добавления члена (insertUser)

Назначение: Предоставить информацию о добавлении члена для внешнего интерфейса, в основном используется при регистрации члена на внешнем интерфейсе

В классе UserDao.java добавьте метод insertUser с тремя параметрами username, phone и password. Возвращает карту. Код выглядит следующим образом:

public int insertUser(String username, String password){  
    final String sql = "insert into tb_user (username, phone, password) values ('"+username+"', '"+username+"', '"+password+"')";  
    KeyHolder keyHolder = new GeneratedKeyHolder();  
    jdbcTemplate.update(new PreparedStatementCreator() {  
        public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {  
            PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);  
            return preparedStatement;  
        }  
    }, keyHolder);  

    return keyHolder.getKey().intValue();  
}  

Анализ кода:

  • Сначала проверьте, существует ли пользователь с указанным номером телефона.
  • Если он существует, сообщите, что пользователь уже существует.
  • Запишите информацию о пользователе в таблицу пользователей.
  • Запись выполнена успешно, верните result как true, запись не удалась, верните result как false.

В классе UserService.java добавьте метод insertUser, возвращающий карту товаров. Код выглядит следующим образом:

@Transactional  
public Map<String, Object> insertUser(String username, String password){  
    Map<String, Object> resultMap = new HashMap<String, Object>();  
    //1、Проверка правильности переданных параметров  
    if (username==null||username.equals("")){  
        resultMap.put("result", false);  
        resultMap.put("msg", "Имя пользователя не может быть пустым!");  
        return resultMap;  
    }  

    int user_id = iUserDao.insertUser(username, password);  

    if (user_id<=0){  
        resultMap.put("result", false);  
        resultMap.put("msg", "Операция базы данных не выполнена успешно!");  
        return resultMap;  
    }  

    resultMap.put("user_id", user_id);  
    resultMap.put("username", username);  
    resultMap.put("phone", username);  
    resultMap.put("password", password);  
    resultMap.put("result", true);  
    resultMap.put("msg", "");  
    return resultMap;  
}  

Примечание: В этом сервисе есть бизнес-логика, если преобразование Json неверно или переданные параметры пусты, это может привести к ошибке записи базы данных, поэтому перед передачей в слой Dao необходимо выполнить проверку. Разобранная json также должна быть проверена, имя пользователя и номер телефона не могут быть пустыми.

4.5.8. Добавление метода login в класс UserController.java

Необходимо добавить аннотацию @RequestMapping, где значение = "/login", и вернуть карту. Код выглядит следующим образом:

@RequestMapping(value = "/login", method = RequestMethod.POST)  
public Map<String, Object> login(String username, String password, HttpServletRequest httpServletRequest){  
    //1、Получение информации о пользователе  
    Map<String, Object> userMap = new HashMap<String, Object>();  
    userMap = iUserService.getUser(username, password);  

    //2、Не удалось получить информацию о пользователе, записать информацию о пользователе  
    if (!(Boolean) userMap.get("result")){  
        userMap = iUserService.insertUser(username, password);  
    }  

    //3、Запись в сеанс  
    HttpSession httpSession = httpServletRequest.getSession();  
    String user = JSON.toJSONString(userMap);  
    httpSession.setAttribute("user", user);  

    Object o = httpSession.getAttribute("user");  

    //4、Возврат информации  
    return userMap;  
}  

Анализ кода:
Логин: использование методов getUser и insertUser

Логин использует методы getUser для поиска пользователя по данным, введённым на фронтенде (телефон и пароль), и insertUser для регистрации нового пользователя.

В реальных проектах обычно есть два этапа проверки:

  1. Проверка с помощью кода, который отправляется пользователю через SMS и служит для подтверждения номера телефона.
  2. Проверка с использованием изображения, которая защищает от массовых рассылок SMS.

После успешной авторизации данные о пользователе записываются в сессию.

4.6. Создание сервиса заказов (порт 4000)

4.6.1. Структура таблиц для заказов

  1. Основная таблица заказов: содержит информацию о заказе, такую как общая сумма, данные о клиенте, способ оплаты и время оплаты.

  2. Таблица деталей заказа: связана с основной таблицей и содержит подробную информацию о каждом элементе заказа, например, количество, цену и общую стоимость каждого товара.

  3. Таблица состояния доставки заказа: связана с основной таблицей, содержит информацию о статусе доставки каждого элемента заказа, включая компанию-перевозчика, статус доставки и подтверждение получения.

4.6.2. Создание сервиса заказов

Шаги для создания сервиса:

  1. Выбрать File — New — Module...
  2. В открывшемся окне выбрать Spring initializr.
  3. Выбрать Module SDK.
  4. Нажать Next.
  5. Указать Group как com.itheima, Artifact как leyou, Name как leyouOrder, Description как Order For leyou Project.
  6. Нажать Next.
  7. Выбрать Spring Cloud Discovery и Eureka Discovery Client.
  8. Нажать Next.
  9. Указать Module name как leyouOrder и Content root как путь + leyouOrder.
  10. Нажать Finish.

После выполнения этих шагов в папке проекта будет создана папка leyouOrder.

4.6.3. Конфигурация pom.xml

В созданном проекте открыть файл pom.xml и настроить зависимости.

spring-cloud-starter-netflix-eureka-client — зависимость для использования Eureka клиента. spring-boot-starter-web — зависимость для разработки веб-модулей. alibaba.fastjson — зависимость для работы с JSON данными. mysql-connector-java и spring-boot-starter-data-jpa — зависимости для подключения к MySQL базе данных.

<?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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <groupId>com.itheima</groupId>
    <artifactId>leyou</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>leyouOrder</name>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

4.6.4. Конфигурация файла application.properties

Открыть файл src\man\resources\application.properties и настроить порт и другие параметры.

server.port=4000
spring.application.name=leyou-order
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/code1_2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

#redis数据库编号, существует 0~15, всего 16 баз данных
spring.redis.database=0
#redis服务器IP
spring.redis.host=127.0.0.1
#redis端口号
spring.redis.port=6379
#redis密码
``` **spring.redis.password=leyou**

# redis запрос тайм-аут, если превышено это значение, redis автоматически разорвёт соединение
**spring.redis.timeout=10000ms**

# максимальное количество соединений jedis, если превышено, будет выдано исключение о недоступности соединения
**spring.redis.jedis.pool.max-active=32**

# максимальный тайм-аут jedis, превышение вызовет исключение тайм-аута соединения
**spring.redis.jedis.pool.max-wait=10000ms**

# максимальное число неактивных соединений jedis
**spring.redis.jedis.pool.max-idle=32**

# минимальное число неактивных соединений jedis
**spring.redis.jedis.pool.min-idle=0**

В созданном проекте откройте файл src\main\java\com.itheima.leyou\leyouOrderApplication и в уже существующем классе запуска добавьте @EnableEurekaClient для запуска Eureka-клиента.
```java
@SpringBootApplication
@EnableEurekaClient
public class OrderApplication {
   public static void main(String[] args) {
      SpringApplication.run(OrderApplication.class, args);
   }
}

После тестирования запуска службы заказа в центре регистрации Eureka можно увидеть сервис leyou-Order, что свидетельствует об успешном запуске службы.

4.6.5. Создание папок controller, service, dao и файлов OrderController.java, OrderService.java и OrderDao.java

Создайте файл интерфейса Dao слоя IOrderDao.

public interface IOrderDao{}

Добавьте аннотацию @Repository к классу OrderDao.java для обозначения компонента доступа к данным (DAO).

@Repository
public class OrderDao implements IOrderDao {}

Объявите метод JDBCTemplate для подключения к базе данных.

@Autowired
JdbcTemplate jdbcTemplate;

Создайте интерфейс Service слоя IOrderService.

Добавьте аннотацию @Service к классу OrderService.java для реализации интерфейса IOrderService.

@Service
public class OrderService implements IOrderService {}

Включите ссылку на IOrderDao.

@Autowired
private IOrderDao iOrderDao;

Добавьте аннотацию @RestController к классу OrderController.java.

Включите ссылку на IOrderService.

@Autowired
private IOrderService iOrderService;

4.6.6. Добавление метода создания заказа (createOrder)

Этот метод в основном используется для вызова со страницы отправки заказа на переднем конце и записи заказа в очередь.

Обратите внимание: здесь необходимо вернуть order_id, который будет использоваться страницей оплаты для запроса.

public Map<String, Object> createOrder(String sku_id, String user_id){
    Map<String, Object> resultMap = new HashMap<String, Object>();
    //1、проверка sku_id
    if (sku_id==null||sku_id.equals("")){
        resultMap.put("result", false);
        resultMap.put("msg", "Ошибка передачи данных с переднего конца!");
        return resultMap;
    }

    //2、получение политики из redis
    String order_id = String.valueOf(System.currentTimeMillis());
    String policy = stringRedisTemplate.opsForValue().get("LIMIT_POLICY_"+sku_id);
    if (policy!=null&&!policy.equals("")){
        //3、начало времени меньше или равно текущему времени, текущий момент времени меньше или равен концу
        Map<String, Object> policyMap = JSONObject.parseObject(policy, Map.class);

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        String now = restTemplate.getForObject("http://leyou-time-server/getTime", String.class);
        try {
            Date begin_time = simpleDateFormat.parse(policyMap.get("begin_time").toString());
            Date end_time = simpleDateFormat.parse(policyMap.get("end_time").toString());
            Date now_time = simpleDateFormat.parse(now);

            if (begin_time.getTime()<=now_time.getTime()&&now_time.getTime()<=end_time.getTime()){
                int limitQuanty = Integer.parseInt(policyMap.get("quanty").toString());

                //4、счётчик redis
                // +1+1+1=3  4

                if (stringRedisTemplate.opsForValue().increment("SKU_QUANTY_"+sku_id, 1)<=limitQuanty){
                    //5、запись в очередь
                    // tb_order: order_id, total_fee, actual_fee, post_fee, payment_type, user_id, status, create_time
                    // tb_order_detail: order_id, sku_id, num, title, own_spec, price, image, create_time
                    // tb_sku: sku_id, title, images, stock, price, indexes, own_spec

                    String sku = stringRedisTemplate.opsForValue().get("SKU_"+sku_id);
                    Map<String, Object> skuMap = JSONObject.parseObject(sku, Map.class);

                    Map<String, Object> orderInfo = new HashMap<String, Object>();
                    orderInfo.put("order_id", order_id);
                    orderInfo.put("total_fee", skuMap.get("price"));
                    orderInfo.put("actual_fee", policyMap.get("price"));
                    orderInfo.put("post_fee", 0);
                    orderInfo.put("payment_type", 0);
                    orderInfo.put("user_id", user_id);
                    orderInfo.put("status", 1);
                    orderInfo.put("create_time", now);
``` **В запросе представлен код на языке Java.**

**Текст запроса:**

orderInfo.put("sku_id", skuMap.get("sku_id")); orderInfo.put("num", 1); orderInfo.put("title", skuMap.get("title")); orderInfo.put("own_spec", skuMap.get("own_spec")); orderInfo.put("price", policyMap.get("price")); orderInfo.put("image", skuMap.get("images"));

String order = JSON.toJSONString(orderInfo);
try {
    amqpTemplate.convertAndSend("order_queue", order);
}catch (Exception e){
    resultMap.put("result", false);
    resultMap.put("msg", "写入队列异常!");
    return resultMap;
}

}else { //如果超出了计数器,返回商品已经售完了 resultMap.put("result", false); resultMap.put("msg", "3亿9被踢回去了!"); return resultMap; }


**Перевод текста на русский язык:**

orderInfo.put("sku_id", skuMap.get("sku_id")); orderInfo.put("num", 1); orderInfo.put("title", skuMap.get("title")); orderInfo.put("own_spec", skuMap.get("own_spec")); orderInfo.put("price", policyMap.get("price")); orderInfo.put("image", skuMap.get("images"));

String order = JSON.toJSONString(orderInfo);
try {
amqpTemplate.convertAndSend("order_queue", order);
} catch (Exception e) {
resultMap.put("result", false);
resultMap.put("msg", "Ошибка при записи в очередь!");
return resultMap;
}

} else {
//если счётчик превышен, товар распродан
resultMap.put("result", false);
resultMap.put("msg", "Товар 39 распродан!");
return resultMap;
}


```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

В папке src\main\java\com\itheima\leyou создайте папку queue, а в ней — файл OrderQueue.java.

orderqueue1.png

@Component
public class OrderQueue {

    @Autowired
    private IOrderService iOrderService;

    @RabbitListener(queues = "order_queue")
    public void insertOrder(String msg){
        //1、получаем сообщение и выводим его
        System.out.println("order_queue получил сообщение: " + msg);

        //2、вызываем метод записи заказа
        Map<String, Object> orderInfo = JSONObject.parseObject(msg, Map.class);
        Map<String, Object> resultMap = new HashMap<String, Object>();
        resultMap = iOrderService.insertOrder(orderInfo);

        //3、если запись не удалась, выводим сообщение об ошибке
        if (!(Boolean) resultMap.get("result")){
            System.out.println("Обработка сообщения order_queue завершилась неудачно:");
        }

        //4、в случае успеха выводим сообщение
        System.out.println("Сообщение order_queue обработано успешно!");
    }
}

4.6.9. Тестирование создания заказа

Сначала приостановите проверку сеанса и присвойте значение user_id равное 1.

Введите в браузере http://localhost:4000/createOrder/26816294479.

createOrder.png

createOrder1.png

Очередь также успешно обработана.

4.6.10. Создание метода запроса заказа (getOrder)

Назначение: запрос информации о заказе для использования в разделе «Мой заказ» на сайте.

Структура таблицы заказов: основная таблица заказов, таблица деталей заказов, таблица состояния заказов. Один заказ имеет одну основную таблицу, несколько деталей и несколько состояний.

Добавьте метод getOrder в класс OrderDao.java, который принимает параметр orderid и возвращает список заказов в виде карты. Код выглядит следующим образом:

public ArrayList<Map<String, Object>> getOrder(String order_id){
    String sql = "select d.sku_id, m.order_id, d.price " +
            "from tb_order m inner join tb_code d on m.order_id = d.order_id " +
            "where m.order_id = ?";
    return (ArrayList<Map<String, Object>>) jdbcTemplate.queryForList(sql, order_id);
}

Добавьте метод getOrder в классе StorageService.java, возвращающий карту товаров. Код следующий:

public Map<String, Object> getOrder(String order_id){
    Map<String, Object> resultMap = new HashMap<String, Object>();

    if (order_id==null||order_id.equals("")){
        resultMap.put("result", false);
        resultMap.put("msg", "Некорректный ввод параметра!");
        return resultMap;
    }

    ArrayList<Map<String, Object>> list = iOrderDao.getOrder(order_id);
    resultMap.put("order", list);
    resultMap.put("result", true);
    resultMap.put("msg", "");
    return resultMap;
}

Добавьте метод getOrder в классе StorageController.java с аннотацией @RequestMapping, где value = "/getOrder/{orderid}", и верните карту товаров. Код такой:

@RequestMapping(value = "/getOrder/{order_id}")
public Map<String, Object> getOrder(@PathVariable("order_id") String order_id){
    return iOrderService.getOrder(order_id);
}

4.6.11. Создание метода обновления статуса заказа (updateOrderStatus)

Основное применение: метод обновления очереди для изменения статуса заказа. В классе OrderDao.java добавьте метод updateOrderStatus. Код:

public boolean updateOrderStatus(String order_id){
    String sql = "update tb_order set status = 2 where order_id = ?";
    return jdbcTemplate.update(sql, order_id)==1;
}

4.6.12. Создание метода оплаты заказа (payOrder)

Основная цель: вызов из интерфейса оплаты и обновление статуса заказа в очереди.

В классе OrderService.java добавьте метод payOrder.

public Map<String, Object> payOrder(String order_id, String sku_id){
    Map<String, Object> resultMap = new HashMap<String, Object>();
    if (order_id==null||order_id.equals("")){
        resultMap.put("result", false);
        resultMap.put("msg", "Ошибка в заказе!");
        return resultMap;
    }

    boolean result = iOrderDao.updateOrderStatus(order_id);

    if (!result){
        resultMap.put("result", false);
        resultMap.put("msg", "Обновление статуса заказа не удалось!");
        return resultMap;
    }

    amqpTemplate.convertAndSend("storage_queue", sku_id);

    resultMap.put("result", true);
    resultMap.put("msg", "");
    return resultMap;
}

В классе OrderController.java добавьте метод payOrder:

@RequestMapping(value = "/payOrder/{order_id}/{sku_id}")
public Map<String, Object> payOrder(@PathVariable("order_id") String order_id, @PathVariable("sku_id") String sku_id){
    //В нормальных условиях здесь будет вызываться интерфейс оплаты, но мы имитируем успешный возврат данных
    boolean isPay = true;
    Map<String, Object> resultMap = new HashMap<String, Object>();
    if (!isPay){
        resultMap.put("result", false);
        resultMap.put("msg", "Сбой вызова интерфейса оплаты!");
        return resultMap;
    }

    return
``` **4.6.13. Тестирование запроса на заказ**

Прямой доступ к странице через метод getOrder, http://localhost:4000/getOrder/1565019341112.

**4.7. Создание шлюза сервиса (порт 80)**

------

**4.7.1. Создание шлюза сервиса**

*Шаг 1.* Выберите File-New-Module... В появившемся окне выберите Spring initializr, выберите Module SDK, затем Next.

*Шаг 2.* Group  com.itheima, Artifact  leyou, Name  ZuulServer, Description  Zuul For leyou Project, затем Next.

*Шаг 3.* Выберите Spring Cloud Discovery, затем Eureka Discovery Client, затем Next.

*Шаг 4.* Module name  ZuulServer, Content root  путь + ZuulServer. Выберите Finish. В папке проекта будет создана папка ZuulServer.

**4.7.2. Конфигурация pom.xml**

В созданном проекте откройте pom.xml и настройте зависимости. spring-cloud-starter-netflix-eureka-client  это jar-пакет, который включает клиент Eureka в проект. spring-boot-starter-web  это jar-пакеты, которые используются для разработки веб-модулей в среде web.

```xml
<?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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <groupId>com.itheima</groupId>
    <artifactId>leyou</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>leyouZuul</name>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </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-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

4.7.3. Конфигурация файла application.properties

Откройте файл src\man\resources\application.properties в созданном проекте и настройте порт и другие параметры.

server.port=80
spring.application.name=leyou-zuul
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/

#忽略框架默认的服务映射路径
zuul.ignored-services='*'
#不忽略框架与权限相关的头信息
zuul.ignore-security-headers=false

zuul.host.socket-timeout-millis=60000
zuul.host.connect-timeout-millis=60000

zuul.host.max-total-connections=500

zuul.routes.leyou-client.path=/leyouClient/**
zuul.routes.leyou-client.serviceId=leyou-client
#防止session不一致问题
zuul.routes.leyou-client.sensitiveHeaders="*"

zuul.routes.leyou-order.path=/leyouOrder/**
zuul.routes.leyou-order.serviceId=leyou-order
zuul.routes.leyou-order.sensitiveHeaders="*"

zuul.routes.leyou-user.path=/leyouUser/**
zuul.routes.leyou-user.serviceId=leyou-user
zuul.routes.leyou-user.sensitiveHeaders="*"

zuul.routes.leyou-stock.path=/leyouStock/**
zuul.routes.leyou-stock.serviceId=leyou-stock
zuul.routes.leyou-stock.sensitiveHeaders="*"

zuul.routes.leyou-storage.path=/leyouStorage/**
zuul.routes.leyou-storage.serviceId=leyou-storage
zuul.routes.leyou-storage.sensitiveHeaders="*"

zuul.routes.leyou-time-server.path=/leyouTimeServer/**
zuul.routes.leyou-time-server.serviceId=leyou-time-server
zuul.routes.leyou-time-server.sensitiveHeaders="*"

Обратите внимание, что zuul использует порт 80, поэтому при доступе к нему не нужно указывать номер порта.

В файле src\main\java\com.itheima.leyou\leyouTimeServerApplication добавьте код в уже существующий класс запуска. Текст запроса:

public static void main(String[] args) { SpringApplication.run(ClientApplication.class, args); }


**Перевод текста на русский язык:**

Паблик статик воид мэйн (стрэнг[] аргс) {
Спринг аппликейшн точка рун (КлиентАппликатион точка класс, аргс);
}
``` **Внимание: в application.properties в spring.cloud.config.server.native.search-locations указывается путь к локальному файлу, который указывает на shared, в большинстве программных компаний этот элемент может находиться на каком-либо сервере или в удалённом репозитории.**

**Файлы в папке share имеют следующие правила именования: имя файла конфигурации для каждого сервиса формируется из spring.application.name в каждом конфигурационном файле сервиса и значения spring.cloud.config.profile.**

Например: leyou-time-server — это имя spring.application.name для времени обслуживания (leyouTimeServer), а значение spring.cloud.config.profile в файле конфигурации приложения — dev, поэтому файл конфигурации для товарного сервиса называется leyou-time-server-dev.properties.

Содержание файла leyou-time-server-dev.properties:

```properties
server.port=8000
spring.application.name=leyou-time-server
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/

В созданном проекте откройте src\main\java\com.itheima.leyou\leyouConfigApplication. В уже созданном классе запуска добавьте @EnableEurekaClient для запуска Eureka Client и @EnableConfigServer для запуска Config Configuration Service.

@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class ConfigApplication {

   public static void main(String[] args) {
      SpringApplication.run(ConfigApplication.class, args);
   }

}

На примере товарного сервиса:

  1. Добавьте зависимость времени обслуживания:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-client</artifactId>
</dependency>
  1. Переместите содержимое исходного файла main/resources/application.properties товарного сервиса в новый файл bootstrap.properties:
spring.application.name=leyou-time-server
spring.cloud.config.profile=dev
spring.cloud.config.uri=http://localhost:2000
spring.cloud.config.label=master
spring.profiles.active=dev
  1. Запустите Config Server и товарный сервис. На Eureka Registration Center вы увидите leyou-config-server и leyou-stock сервисы, что означает успешную настройку сервисов.

  2. Измените файл конфигурации времени обслуживания (leyouTimeServer) application.properties, изменив порт на 8001:

server.port=8001
spring.application.name=leyou-time-server
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/

Запустите Config Server, затем время обслуживания. Вы увидите, что запускается порт 8000.

Не запуская Config Server, запустите время обслуживания. Вы увидите, что запустится порт 8001.

Вывод: при запуске сервиса сначала используется bootstrap.properties, если его нет, то используется application.properties. Это доказывает, что bootstrap имеет более высокий приоритет, чем application.

Пять. Проект тестирования

5.1. Создание политики ограничения продаж

Адрес: http://localhost/leyouClient/page/limitPolicyPage.html

Выберите товар, введите цену продажи, количество товаров в продаже, дату начала и окончания продаж, нажмите «Сохранить».

5.2. Страница входа

Адрес: http://localhost/leyouClient/page/loginPage.html

5.3. Страница списка товаров

Войдите на страницу входа, введите номер телефона и пароль, перейдите на страницу списка товаров.

Адрес: http://localhost/leyouClient/page/stockListPage.html

5.4. Страница товара

Перейдите на страницу списка товаров и выберите товар.

Адрес: http://localhost/leyouClient/page/stockDetailPage.html?sku_id=27359021557

5.5. Отправка заказа

На странице товара нажмите «Купить сейчас».

Адрес: http://localhost/leyouClient/page/createOrderPage.html?sku_id=27359021557

5.6. Платёжная страница

На странице отправки заказа нажмите «Отправить заказ».

Адрес: http://localhost/leyouClient/page/payPage.html?order_id=1565061554849

Нажмите «Оплата через WeChat», появится сообщение «Платёж выполнен успешно».

О очереди:

orderqueue.png

storagequeue.png

Шесть. О собеседовании

6.1. Над какими проектами вы работали раньше?

Ответ: проекты торгового центра, система продаж торгового центра. Система продаж торгового центра является ключевым маркетинговым мероприятием торгового центра, поэтому она была выделена в отдельную систему. Я был главным разработчиком системы продаж.

6.2. Был ли проект запущен? Можно ли получить доступ к внешней сети?

Ответ: проект уже запущен, но мы являемся аутсорсинговой компанией, и после завершения разработки проекта мы не знаем, где он будет развёрнут, поэтому мы не можем получить к нему доступ.

6.3. Какова прибыль от проекта?

Ответ: поскольку я разработчик, доход от этого проекта контролируется отделом продуктов, поэтому я не очень хорошо понимаю, как распределяется доход.

6.4. Сколько человек в команде проекта? Как распределяются обязанности?

Ответ: в команде 10 человек, один дизайнер, шесть разработчиков Java, один фронтенд-разработчик, два тестировщика. Я отвечаю за общую архитектуру и часть кода в группе из шести разработчиков. Обычно дизайнер пишет общий план проектирования, включая требования к исследованию, описание проекта, рабочий процесс, используемую технологию архитектуры и диаграмму архитектуры. Мы разрабатываем более подробный дизайн, включая структуру таблицы, бизнес-процессы, которые может обрабатывать каждый микросервис, и даже псевдокод. Фронтенд отвечает за разработку страницы, мы предоставляем версию для тестирования, а затем передаём её тестировщику.

6.5. С какими проблемами вы столкнулись в проекте? Как вы их решали?

Ответ:

  1. Техническая архитектура: изначально мы использовали базу данных для управления перепродажей и дефицитом товаров, но это вызвало серьёзные проблемы с производительностью во время стресс-тестирования, поскольку база данных вызвала множество взаимоблокировок, и передняя страница перестала отвечать. Позже мы использовали технологию кэширования Redis для решения проблемы перепродажи и дефицита товаров, помещая все товары и запасы, участвующие в распродаже, в кэш Redis, а затем уменьшая количество запасов при каждой продаже, обрабатывая всё в кэше, эффективно решая проблему производительности.

  2. Разработка: когда я разрабатывал код для уменьшения запасов, запасы не могли быть уменьшены должным образом, поэтому я сначала проверил журнал консоли, чтобы увидеть, есть ли сообщения об ошибках, проанализировал их, а затем установил точки останова, чтобы отследить код построчно и найти проблему.

  3. Взаимодействие с фронтендом: данные, полученные фронтендом от нашего интерфейса, не могут быть проанализированы, поэтому нам необходимо вывести данные, возвращаемые нашим интерфейсом, чтобы проверить, есть ли искажённые символы, неправильный формат или неправильные поля, а также необходимо совместно отладить с фронтендом.

  4. Тестирование: после того как тестировщик предоставит отчёт о тестировании, некоторые проблемы трудно воспроизвести, сначала необходимо использовать их данные для имитации, посмотреть, является ли это проблемой данных, затем использовать их среду для отслеживания кода, чтобы найти проблему.

6.6. Как обрабатываются распределённые транзакции?

Ответ: распределённые транзакции обрабатываются с помощью TCC. Режим обработки: пример с заказами и запасами

Обработка происходит в режиме, который включает в себя заказы и запасы. Логика try для сервисов заказов и запасов выполняется после открытия транзакции. У всех всё работает нормально: сервис заказов выполняет бизнес-код, затем логику confirm, запасы также выполняют бизнес-код и логику confirm. Все вместе отправляют данные. Если логика try для сервиса запасов не срабатывает, сервис заказов получает уведомление о выполнении логики cancel.

В случае, когда ни у кого нет проблем, требуется провести одно взаимодействие через try и ещё одно через confirm. Поэтому координация между двумя сервисами имеет свою стоимость. Когда объём невелик, это не проблема, но когда объём становится большим, возникает эффект очереди.

Поэтому мы не используем распределённую обработку транзакций между несколькими микросервисами. Сначала мы обрабатываем запросы через Redis, а затем разделяем бизнес-процессы. Для решения задач мы используем промежуточное ПО — очередь сообщений RabbitMQ. Каждый сервис открывает собственную обработку транзакций, что позволяет более эффективно решать проблемы параллелизма.

6.7. Сколько времени заняло разработка проекта?

Ответ: около 10 дней ушло на предварительное исследование и общее проектирование. На подробное проектирование ушло примерно 5 дней. Разработка заняла около 20 дней, плюс тестирование, отладка, исправление ошибок, повторное тестирование и запуск должны занять примерно полтора месяца.

6.8. Какие технологии использовались в проекте?

Ответ: используется архитектура микросервисов SpringCloud для решения проблемы перегрузки бэкэнда. В соответствии с различными модулями происходит разделение, каждая команда разработчиков отвечает за один микросервис. Используется промежуточное ПО Redis для кэширования и RabbitMQ для разделения бизнес-процессов.

6.9. Данные в проекте извлекаются только через SQL-запросы?

Ответ: не совсем. Например, политика мгновенных распродаж товаров извлекается из Redis, а не через SQL-запрос. Список товаров и их детали извлекаются через SQL-запросы. Количество товаров, участвующих в распродаже, контролируется через Redis, а не с помощью SQL-запросов в реальном времени.

6.10. Какой модуль в проекте был самым сложным?

Ответ: обработка бизнес-логики модуля запасов была самой сложной. Запасы — это чувствительные данные, и все операции должны выполняться очень осторожно. Ошибки в запасах могут привести к значительным потерям для бизнеса.

Основная сложность этого проекта — как справиться с большими объёмами параллельных запросов. Мы контролируем перепродажу и дефицит через Redis и разделяем бизнес-операции через очередь сообщений RabbitMQ.

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Описание недоступно Развернуть Свернуть
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/happydts-seckill.git
git@api.gitlife.ru:oschina-mirror/happydts-seckill.git
oschina-mirror
happydts-seckill
happydts-seckill
master