Java простое веб-приложения. Логин и регистрация

Всем доброго времени суток.

Эта статья рассчитана для новичков в мире Web и Java. Мы рассмотрим стандартную архитектуру веб-приложения и сделаем небольшой проект с нуля.

Будем использовать Spring (Огромный фреймворк для веба), но по минимуму. Если не иметь предыдущего опыта использования и сразу пытаться использовать весь функционал, то не будет базового понимания, так как имеется перечень подводных камней.

Что будет делать приложение

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

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

Исходный код вы сможете найти здесь.

Что будем использовать

  • любимую IDE (я использую IntelliJ IDEA)
  • не замутненную голову
  • Maven (сборщик проекта)
  • JDBC (звено между Java и СУБД)

Создание проекта

Создайте простой Maven проект. Делаем все с чистого листа, не добавляя никаких архетипов.
IDE должна сгенерировать такую структуру.

image

И так, что мы видим.

  • java — основное место где будет происходить "магия"
  • resources — место где хранятся наши ресурсы. (Это могут быть файлы, картинки и чего только душа пожелает, зачастую тут хранится front часть приложения)
  • test — место где принято писать тесты к приложению. (Вы можете с чистой совестью удалять эту папку, тестирование мы рассматривать не будем)
  • pom.xml — сердце Maven. В нем содержится информация о проекте, тут прописываются все зависимости, указывается какой тип "упаковки" должен быть у проекта и тд.

Кстати, не пропустите сообщения от IDE, и разрешите авто-импорт для Maven.

image

Архитектура веб-приложения

Чтобы начать кодить, нужно подкрепиться немного теорией.
Простите за мой дизайнерский талант, я в этом не силен.

image

На картинке показаны слои веб-приложения, каждый слой может взаимодействовать только со своими соседями, если приложение написано "по-правильному".

И так пробежимся по каждому слою и разберем их назначения.

  1. Browser — это лицо нашего приложения. То что видит пользователь и что называют Frontend. Он может взаимодействовать с Backend — серверной частью, а именно с контроллером. Этот слой должен уметь отправлять и получать данные от контроллера.
  2. Controller — должен контролировать запросы от Frontend, уметь их обрабатывать и возвращать данные. И, конечно же, задачей контролера является общение со следующим слоем.
  3. Service — в этом слое должна быть реализована бизнес-логика приложения. Можно считать этот слой корнем приложения. В нем происходят самые тяжеловесные операции.
  4. DAL(data access layer) — этот слой должен общаться с нашей БД. В нём реализованы все запросы к БД, которые нужны для приложения, а именно запросы на выборку, изменения и тд.
  5. Database — она же БД, структурированная туса данных.

Проектирование базы данных

Один из самых первых этапов разработки веб-приложения является проектирование БД.

Выбор БД предоставляется на ваше усмотрение, но у меня в примере будет использоваться H2.

Так как я хочу чтобы пример вышел небольшой, но чтобы в нем удалось рассмотреть как можно больше скользких моментов и понять всеобщие принципы построения веб-приложений. В примере будет только одна таблица User.

create table DATABASE.USER (     ID INT auto_increment,     NAME VARCHAR not null,     SURNAME VARCHAR not null,     LOGIN VARCHAR not null,     PASSWORD VARCHAR not null,     constraint USER_PK         primary key (ID) ); 

Добавляем Spring в проект

Вспоминаем про нашу папку java.

Создаем внутри java пакет com.zuk(вы можете называть иначе), в нем класс App с обычной main функцией.

И добавим щепотку магии Spring. Не будем вдаваться сильно в то что здесь происходит, с помощью @SpringBootApplication мы просто говорим Spring, чтобы он делал автоматическую настройку, чтобы он сканировал все что ему нужно. Сейчас рекомендую рассматривать Spring как автономную республику.

//com.zuk.App @SpringBootApplication public class App {      public static void main(String[] args) {         SpringApplication.run(App.class, args);     } }

Не впадаем в панику если все красное и не забываем подключать зависимости в 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>      <groupId>org.zuk</groupId>     <artifactId>LoginAndRegistration</artifactId>     <version>1.0-SNAPSHOT</version>      <properties>         <maven.compiler.source>1.8</maven.compiler.source>         <maven.compiler.target>1.8</maven.compiler.target>     </properties>      <dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>             <version>2.2.5.RELEASE</version>         </dependency>     </dependencies>  </project>

Если вы все сделали правильно у вас появится возможность сделать билд проекта, и Spring скажет вам привет.

image

Spring говорит что он стартанул сервер на порту 8080. Мы можем сами убедиться в этом, сходив на http://localhost:8080.

Подключаем БД к проекту

И так мы уже создали нашу Базу даных, подключили Spring к проекту, но они ещё не знают о существование друг друга.

Давайте создадим application.properties по пути src.main.resources.

Этот файл служит для хранения конфигурационных параметров в формате ключ-значение.

spring.datasource.driverClassName=org.h2.Driver spring.datasource.url=jdbc:h2:~/test  spring.datasource.username=sa spring.datasource.password=

Это стандартные ключи для Spring, и он сам может их прочитать.

Чтобы вы смогли убедиться в соединении, предлагаю проверить сможем ли мы подключиться в БД.
Хочу предупредить, моё решение будет спорным. На мой взгляд для новичка оно будет показательным и не будет вызывать много вопросов, но не стоит сильно опираться на данный подход. В Spring есть интересные реализации для работы с БД.
Советую почитать несколько статей по данной тематике.

По пути java.com.zuk.connection создадим класс ConnectionManager, он должен отдавать нам соединение с БД, и с этим соединением мы в дальнейшем будем работать.
Для этого нам понадобится вытянуть наши properties, сделаем это с помощью FileInputStream.

//ConnectionManager FileInputStream fis; Properties property = new Properties();

Проберемся к нашему application.properties, и вытянем с него properties.

//ConnectionManager fis = new FileInputStream("src/main/resources/application.properties"); property.load(fis);

Создаем метод getConnection.

  //ConnectionManager   public Connection getConnection() {             Connection connection = null;             try {                 connection = DriverManager.getConnection(property.getProperty("spring.datasource.url"),property.getProperty("spring.datasource.username"),property.getProperty("spring.datasource.password"));             } catch (SQLException e) {                 e.printStackTrace();             }             return connection;         }

Теперь можете проверить есть ли соединение, сделать это можно и в App.

Если у вас все получилось, должно вывести такое.

conn2: url=jdbc:h2:~/test user=SA

Ах да, не забываем добавить зависимость в наш любимый pom.

<dependency>     <groupId>com.h2database</groupId>     <artifactId>h2</artifactId>     <version>1.4.200</version> </dependency>

POJO (Plain Old Java Objects)

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

Представьте простой класс, который ничего не наследует, в который не должно быть реализованы ничего сложно. Это просто класс, который служит носителем информации, в котором есть некоторые поля, геттеры и сеттеры для них. Максимум, что может быть, это реализация стандартных методов объекта. Это и есть наш POJO.

Для нашего проекта, это будет описание сущности таблицы User.
То есть мы реализуем таблицу в виде класса.

И так создаем пакет java.com.zuk.entity, в нем класс User.
Запишем в поля класса поля таблицы, и реализуем для каждого геттер и сеттер.

User.java

public class User {      int id;     String name;     String surname;     String login;     String password;      public int getId() {         return id;     }      public void setId(int id) {         this.id = id;     }      public String getName() {         return name;     }      public void setName(String name) {         this.name = name;     }      public String getSurname() {         return surname;     }      public void setSurname(String surname) {         this.surname = surname;     }      public String getLogin() {         return login;     }      public void setLogin(String login) {         this.login = login;     }      public String getPassword() {         return password;     }      public void setPassword(String password) {         this.password = password;     } }

DAL

Что же, самое время приступить к следующему слою, а именно DAL. Тут будут реализованы все запросы к нашей БД, которые нам понадобятся. Честь этого слоя будет отстаивать DAO (data access object) — интерфейс к какому-либо типу базы данных или механизму хранения.

Создаем пакет java.com.zuk.dao в нем интерфейс UserDao (принято сначала писать имя таблицы). Опишем пока что только два метода findByLogin, save.

public interface UserDao {     User findByLogin(User user);     Boolean save(User user); }

Дальше напишем реализацию для этого интерфейса, создаем пакет java.com.zuk.dao.impl,
в нем класс UserDaoImpl.
Вы можете спросить зачем так сильно разделять все — это будет служить хорошей привычкой, вдруг вам придется работать в большом проекте, там скорее всего будут специфические реализации (например, если используется несколько БД).

public class UserDaoImpl implements UserDao {     @Override     public User findByLogin(User user) {         return null;     }      @Override     public Boolean save(User user) {         return null;     } }

Начнем с findByLogin. Нам нужно сделать такой SQL запрос.

SELECT  * from DATABASE.User where LOGIN=login

Далее нужно вернуть объект класса User.

Проверим есть ли у нас соединение с базой, если же его не будет мы вернем null.

//UserDaoImpl ConnectionManager cm = new ConnectionManager(); Connection con = cm.getConnection();  @Override     public User findByLogin(String login) {         User user = null;         if (con != null) {               //..           }         return user;      }

Если условие в if правильное, мы выполним SQL запрос, и запишем значение в user.

//findByLogin PreparedStatement pr = con.prepareStatement("SELECT * FROM DATABASE.User where LOGIN=?"); pr.setString(1 , login); ResultSet resultSet = pr.executeQuery();//return sql result if(resultSet.next()) {        user = new User();        user.setId(resultSet.getInt("ID"));        user.setName(resultSet.getString("NAME"));        user.setSurname(resultSet.getString("SURNAME"));        user.setLogin(login);        user.setPassword(resultSet.getString("PASSWORD"));        return user; }  pr.close();  con.close();

Не забываем покрывать возможные исключения.

Попробуйте реализовать save своими руками.
Код оставлю под спойлером.

save

//UserDaoImpl @Override     public Boolean save(User user) {         ConnectionManager cm = new ConnectionManager();         Connection con = cm.getConnection();         if (con != null) {             try {                 PreparedStatement pr = con.prepareStatement("insert into DATABASE.USER (NAME,SURNAME,LOGIN,PASSWORD) values (?,?,?,?)");                 pr.setString(1,user.getName());                 pr.setString(2,user.getSurname());                 pr.setString(3,user.getLogin());                 pr.setString(4, DigestUtils.md5DigestAsHex((user.getPassword()).getBytes()));                 pr.executeUpdate();                 pr.close();                 con.close();                 return true;             } catch (SQLException e) {                 e.printStackTrace();              } catch (Exception e) {                 e.printStackTrace();             }         }         return false;     }

Service

В сервисе будет находится наша бизнес-логика. Создаем пакет java.com.zuk.service, в нем интерфейс UserService с двумя методами: login, registration. И создаем класс UserServiceImpl в java.com.zuk.service.impl, который имплементирует UserService.

public class UserServiceImpl implements UserService {     @Override     public String login(User user) {         return null;     }      @Override     public String registration(User user) {         return null;     } } 

Предлагаю начать с login.
Алгоритм будет таким:

  1. Проверим существует ли поле "login" в БД.
  2. Если условие 1 выполняется, проверяем совпадает ли пароль.
  3. Если условие 2 выполняется, скажем "life is beautiful"

//UserServiceImpl UserDaoImpl userDao = new UserDaoImpl();     @Override     public String login(User user) {         User findUser = userDao.findByLogin(user.getLogin());         if(findUser!=null){             if(DigestUtils.md5DigestAsHex((user.getPassword()).getBytes()).equals(findUser.getPassword())){                 return "life is beautiful";             }         }         return "do not give up";     }

Попытайтесь сделать регистрацию своими руками.

registration

 @Override     public String registration(User user) {         User findUser = userDao.findByLogin(user.getLogin());         if(findUser==null) {             userDao.save(user);             return "life is beautiful";         }         return "this login is not available";     }

Controller

Настала звездный час контроллера, и мы наконец-то сможем увидеть результат наших деяний.
Создаём пакет java.com.zuk.controller в нем класс Controller. Перед классом разместим "магическую" аннотацию RestController, она поможет Spring понять, что этот класс будет служить нам как Rest Controller.
Создаём метод, который возвращает строку и аннотируем его с помощью @RequestMapping("/") и @ResponseBody.

@RestController public class Controller {     @RequestMapping("/")     @ResponseBody     String main() {         return "Hello from Controller";     } }

Делаем билд проекта, и переходим на http://localhost:8080.
Мы должны увидеть "Hello from Controller".

Теперь создаём два html файла. Один будет с формой регистрации, второй с формой логина.
Правильным тоном будет сохранение этих файлы внутри resources, а еще лучше в папку resources.static.

html

<!--loginFrom.html--> <html> <title>Login</title> <body> <form action="http://localhost:8080/login">     <input type="text" required name="login" placeholder="login" />     <input type="password" required name="password" placeholder="password" />     <button>login</button> </form> <a href="http://localhost:8080/registrationForm">don't have account</a> </body> </html>

<!--registrationForm.html--> <html> <title>Registration</title> <body> <form action="http://localhost:8080/registration">     <input type="text" required name="name" placeholder="name" />     <input type="text" required name="surname" placeholder="surname" />     <input type="text" required name="login" placeholder="login" />     <input type="password" required name="password" placeholder="password" />     <button>registration</button> </form> <a href="http://localhost:8080/loginForm">already have account</a> </body> </html>

Теперь я хочу, чтобы при переходе по ссылке http://localhost:8080/loginForm, или http://localhost:8080/registrationForm выводились наши html файлы с формами.
Возвращаемся к нашему контроллеру, тут мы должны добавить мапинг для /loginForm, /registrationForm и отдавать соответствующую html. В этом нам поможет ModelAndView.

//Controller  @RequestMapping("/loginForm")     @ResponseBody     ModelAndView loginForm() {         ModelAndView modelAndView = new ModelAndView();         modelAndView.setViewName("loginForm.html");         return modelAndView;     }      @RequestMapping("/registrationForm")     @ResponseBody     ModelAndView registrationForm() {         ModelAndView modelAndView = new ModelAndView();         modelAndView.setViewName("registrationForm.html");         return modelAndView;     }

Если вы заметили, то в форме я уже написал action на http://localhost:8080/login и registration, но мы ещё не ловим эти переходы в контроллере.
Пора это исправлять и связаться с нашим service.

//Controller UserServiceImpl userService = new UserServiceImpl(); //..  @RequestMapping("/login")     @ResponseBody     String login(@RequestParam String login,@RequestParam String password) {         User user = new User();         user.setLogin(login);         user.setPassword(password);         return userService.login(user);     }      @RequestMapping("/registration")     @ResponseBody     String registration(@RequestParam String name,@RequestParam String surname, @RequestParam String login,@RequestParam String password) {         User user = new User();         user.setName(name);         user.setSurname(surname);         user.setLogin(login);         user.setPassword(password);         return userService.registration(user);     }

Мы видим, что наши методы в Controller начали принимать параметры. Это те параметры, которые мы пишем в наших формах. Их имена должны совпадать с именами input.

Заключение

Надеюсь эта статья смогла ответь на некоторые ваши вопросы, и помогла расширить свои горизонты.

Всем приятного развития.

ссылка на оригинал статьи https://habr.com/ru/post/495010/

YouTrack теперь с эмоциями

Привет, я Лена из JetBrains! В текущей ситуации нам с командой YouTrack кажется особенно важным выполнять обещания по тому, что запланировано к выпуску на 2020. Поэтому рада представить YouTrack 2020.1, в котором такую радость и другие эмоции можно теперь выражать с помощью с эмодзи-реакций. Ими теперь можно отвечать на комментарии к задачам, а центр уведомлений будет держать вас в курсе реакций, которые вы получаете.

Для всех, кто хочет внести свой вклад в перевод продукта на другие языки, теперь доступен режим перевода прямо из контекста интерфейса YouTrack. Для администраторов в YouTrack 2020.1 появились новые настройки полей задач, чтобы поддерживать сложные случаи доступов к отдельным полям без написания рабочих процессов. Для разработчиков мы улучшили интеграцию с системами контроля версий.

Подробнее о новшествах — дальше в посте.

Надеюсь, новые функции обрадуют вас также, как мы радовались на каждом стендапе, обсуждая свимлейн «Реакции» на доске.

Реакции на комментарии для всех

Реакции — простой способ быстро ответить на комментарий в YouTrack. Пользователи любят эмодзи за то, что ими можно заменить слово или даже целое предложение — с реакциями можно не писать лишние комментарии при обсуждении задачи.
Чтобы случайно не пропустить важную реакцию, в центре уведомлений есть список недавних реакций на ваши комментарии, оттуда же можно сразу открыть нужную задачу.

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

Режим перевода интерфейса из контекста для иноязычных пользователей

YouTrack переведен на английский, испанский, немецкий, русский, французский и японский языки. Кроме того, можно переключиться на один из языков, перевод на которые осуществляется нашими пользователями. Это венгерский, иврит, итальянский, китайский упрощенный, корейский, польский, португальский и чешский.

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

Процесс перевода виден в реальном времени. Можно переводить даже динамическую часть приложения. После включения эта функция перевода становится доступной для всех пользователей вашего сервера YouTrack. Если вы не хотите отвлекать коллег по команде этой возможностью, просто воспользуйтесь бесплатным тарифным планом и создайте отдельный сервер YouTrack специально для перевода.

Тонкие настройки доступа к полям в задачах для администраторов

Новые настройки доступа к полям в задачах позволяют выбрать отдельные группы пользователей или команды, которые могут просматривать или обновлять значения полей.

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

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

Улучшенная интеграция с VCS и TeamCity для разработчиков

Расширены возможности интеграции YouTrack с TeamCity, Upsource и системами контроля версий, такими как GitHub.

Общие настройки интеграции с VCS для нескольких проектов

Благодаря интеграции можно прикрепить к задаче изменения VCS, в комментариях к которым упоминается идентификатор этой задачи. Теперь можно сначала настроить интеграцию для одного проекта, а затем распространить выбранные параметры на другие проекты, просто выбрав их в настройках. Таким образом, в других проектах тоже отобразятся изменения, прикрепленные к задачам. Чтобы поделиться конфигурацией, просто выберите нужные проекты.

Обработка связанных задач

Интеграция между YouTrack и TeamCity теперь позволяет точнее выбирать, какие задачи соответствуют определенному билду. В рамках интеграции с TeamCity задачи, которые упоминаются в изменениях VCS, включенных в билд, рассматриваются как «связанные» с этим билдом. Когда билд обрабатывается, связанные задачи в статусе “Завершена”, обновляются соответствующим номером сборки. Теперь можно указать условие, которое будет определять, какие связанные задачи следует обновлять.

Поддержка Swagger

Еще одно улучшение, связанное с разработкой: в YouTrack 2020.1 появился встроенный Swagger Open API 3.0 файл. С его помощью можно использовать REST API для интеграции вашего приложения с YouTrack. На этой странице GitHub можно найти совместимые инструменты генерации кода для вашей среды выполнения.

Разрешение «Применить команду без уведомлений»

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

Подробнее о новой версии читайте на сайте.

ссылка на оригинал статьи https://habr.com/ru/company/JetBrains/blog/495012/

Открытые онлайн-материалы от Computer Science центра, часть 2

В первой части поста мы рассказали о наших популярных онлайн-курсах на Stepik, а теперь выкладываем записи открытых лекций и видеокурсов на YouTube и напоминаем о том, что до 11 апреля открыт новый набор в CS центр в Санкт-Петербурге и Новосибирске.


Открытые лекции

1. «Что такое операционная система и как она работает», Кирилл Кринкин

2. «LaTeX: краткое введение в качественную типографику», Александр Смаль

3. «Программирование музыки на Sonic Pi», Владимир Красильщик

4. «Введение в язык программирования Go», Марат Хабибуллин

5. «Жадная гипотеза для задачи о надстроке», Александр Куликов

6. «Введение в язык программирования Rust», Алексей Кладов

7. «Введение в статистическое моделирование с помощью языка программирования Stan», Александр Сухочев

8. «Классические и новые задачи комбинаторики», Андрей Михайлович Райгородский

9. «Случайные графы», Андрей Михайлович Райгородский

10. «Вероятность и алгебра в комбинаторике», Андрей Михайлович Райгородский

Все открытые лекции CS центра

Видеокурсы

Если не знаете, с чего начать, почитайте материал Яндекс.Академии: мы рассказали ребятам про 10 наших самых популярных видеокурсов.

Все выложенные курсы CS центра

ссылка на оригинал статьи https://habr.com/ru/company/JetBrains-education/blog/495014/

Изготовление и применение антикороновирусной лампы

Ультрафиолетовая бактерицидная лампа может применяться для дезинфекционной обработки помещений как одна из мер против короновируса.

«Эффект обеззараживания основан на прямом губительном воздействии ультрафиолетовых лучей в спектре с длиной волны 200—300 нм и максимумом бактерицидного действия 260 нм … ультрафиолетовые лучи могут воздействовать не только на обычные бактерии, но и на споровые организмы и вирусы» — Справочник химика [1].

Подробную информацию об использовании ультрафиолетового излучения для обеззараживания можно найти в [2].

Профессиональные бактерицидные установки стоят недешево и предназначенные для них лампы в обычный патрон не вкрутишь. В этой статье пойдёт речь об изготовлении и применении недорогой бактерицидной лампы со стандартным патроном Е27 или Е14 с питанием от сети 220В на основе УФ лампы с цоколем 2G7 или G11 и электронного балласта б/у энергосберегающей лампы.

Работающая лампа

Меры предосторожности при использовании УФ-лампы.

  1. Воздействие ультрафиолетового излучения на кожу приводит к ожогам разной степени, может вызывать рак кожи. При облучении глаз вызывает ожог роговицы. Ультрафиолет коротковолнового диапазона (100—280 нм) может проникать до сетчатки глаза. Обработка помещений должна проводиться только без людей!
  2. При работе УФ ламп образуется озон, обладающий высокой токсичностью. После обработки помещение необходимо проветрить. Это не относится к УФ лампам из увиоливого стекла, не генерирующим озон по причине поглощения стеклом спектра излучения, создающего молекулы озона.
  3. Многие полимеры, используемые в товарах широкого потребления, деградируют под действием УФ-света. Не рекомендуется надолго оставлять изделия из полимеров вблизи работающих УФ ламп.

В зависимости от соотношения мощностей УФ лампы и электронного балласта, возможны 3 варианта:

  1. Если мощность лампы и балласта совпадают, задача проста: подключить лампу к балласту и прикрепить к корпусу.
  2. Если мощность лампы больше мощности балласта, если повезёт, работать будет, но не на полную мощность, а в соответствии с мощностью балласта. Балласт ограничивает выходной ток, поэтому подключение ламп избыточной мощности не выведет его из строя.
  3. Если мощность лампы меньше — требуется вмешательство в конструкцию балласта с целью уменьшения мощности. Об этом — следующий раздел.

Устройство и работа электронных балластов.

На эту тему написано немало статей. Рассмотрим первую схему из статьи «Схемы, устройство и работа энергосберегающих ламп» [3].

image
Рисунок 1: cхема электронного балласта лампы.

Из всех элементов схемы нас интересуют:

  1. Лампа. На схеме обозначены её катоды LMP1, LMP2. Сюда будем подсоединять УФ-лампу.
  2. Пусковой конденсатор С3. Во время запуска, напряжение на конденсаторе C3 достигает порядка 600В. Если колба энергосберегающей лампы была повреждена, вероятен выход из строя конденсатора C3 и транзисторов. Поэтому, при использовании балласта от неисправной лампы, необходимо проверить их исправность. Да и все остальные детали желательно проверить до первого включения.
  3. Терморезистор RT1 с положительным температурным коэффициентом сопротивления, также называемый позистором или PTC. Устанавливается в некоторых лампах. Он предотвращает перенапряжение на выходе преобразователя: в момент поджига лампы он холодный и протекающий через него ток разогревает катоды лампы, чтобы облегчить запуск, снизить износ, потом PTC нагревается, увеличивает своё сопротивление и не препятствует дальнейшей работе лампы.
  4. Предохранитель F1, необходимый для обеспечения пожаробезопасности.
  5. Выходной дроссель L1. Ограничивает ток через лампу.
  6. Трансформатор обратной связи TR1. Намотан на ферритовом кольце и является насыщающимся. От его параметров зависит частота генерации, а от неё — индуктивное сопротивление дросселя и ток через лампу.

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

В документе «Electronic Lamp Ballast Design» [4] приведена методика расчёта электронных балластов при разработке с нуля. При переделке готовых электронных балластов пригодятся формулы:

  1. Формула (1) на с. 3 — зависимость индуктивного сопротивления от частоты.
  2. Формула (3) на с. 3, и ненумерованная чуть ниже, связывающие индуктивность дросселя и ток через лампу.
  3. Формула (16) на с.8, определяющая частоту генерации.
  4. Формула (18) на с.10, связывающая ток протекающий через лампу с числом витков первичной обмотки и периметром сердечника трансформатора обратной связи. Ток протекающий через лампу равен току первичной обмотки.

    Из этих формул следует, что ток через лампу обратно пропорционален числу витков первичной обмотки трансформатора обратной связи. Чтобы уменьшить ток, нужно домотать больше витков. А увеличивать ток нежелательно — могут не выдержать транзисторы и другие детали.

  5. Формула (6) на с.7 — напряжение на вторичной обмотке трансформатора обратной связи, которое не должно превышать максимальное напряжение базы транзистора.
  6. Лучше добавить обратные диоды в базовые цепи транзисторов, чтобы они не вышли из строя от превышения напряжения базы после домотки трансформатора. Это, вероятно, было причиной неудачи с первым вариантом доработанного балласта: он сгорел с бабахом. Обуглились все резисторы в цепях баз, пробило транзисторы. Дешевая китайская схема, значит, была рассчитана впритык. Шунтирование эмиттерных переходов обратными диодами, которые показаны на схеме красным цветом, предотвратит пробой.

Методика переделки электронных балластов под любую нужную мощность (в меньшую сторону)

  1. Определение тока. Измерьте напряжение U на штатной колбе б/у лампы, мощность которой P1 указана на корпусе. Ток I1 = P1 / U1. Если колба б/у лампы неисправна, примем допущение, что напряжение U1 на старой и новой U2 лампах примерно равны U1 = U2. Ток УФ-лампы I2 = P2 / U2. Соотношение токов I1/I2 определяет изменение числа витков первичной обмотки трансформатора обратной связи.
  2. Домотка первичной обмотки трансформатора обратной связи. Посчитайте количество витков первичной обмотки Np. Нужно домотать N = Np * (I1/I2 — 1) витков.
  3. Добавление обратных диодов в базовые цепи транзисторов. Напряжение и ток диодов малы, поэтому годятся почти любые быстрые диоды. Например, UF4007 или аналогичные, из других б/у балластов.
  4. Добавление терморезистора (если его не было) параллельно пусковому конденсатору.
  5. Добавление предохранителя F1 (если его не было). Номинальный ток предохранителя Iпр = 2P / Uсети выбирается по расчетному току нагрузки с учетом пусковых токов. Можно брать из других б/у балластов такой же или большей мощности.
  6. Испытание. Проводить в защитных очках.
    1. Временно подключить УФ-лампу. При первом включении подсоединить лампу накаливания мощностью 60-100 Вт последовательно с фазой питающей сети для предотвращения выхода из строя балласта в случае допущенных ошибок.
    2. Кратковременно включить питание без добавочной лампы, измерить ток, сравнить с рассчитанным.
    3. Сравнить реальную мощность на лампе с номинальной.
    4. Если номинальная мощность превышена на 2Вт и более, домотать ещё 1 виток первичной обмотки трансформатора обратной связи и повторить этот пункт.

Методика изготовления бактерицидной лампы

  1. Разборка лампы. Подогрейте корпус феном в области шва чтобы пластмасса стала эластичнее, просуньте тупой нож или плоскую отвёртку и отожмите защёлки.
  2. Доработка балласта — описана выше, делается при несовпадении мощностей УФ-лампы и балласта.
  3. Удаление колбы. Отсоедините выводы колбы от платы балласта. Подогрейте феном клей, которым приклеена колба, и расковыряйте его ножом, чтобы отделить колбу от корпуса.
  4. Доработка корпуса и установка УФ-лампы. Конкретные действия зависят от конструкции корпуса. В моём случае оказалось достаточно срезать часть пластика и сделать отверстия для выводов УФ-лампы. После припаивания проводов УФ лампа оказалась достаточно хорошо зафиксирована. Если планируется замена УФ-ламп, установите патрон.
  5. Сборка лампы. Проложите прокладку из изолирующего материала между платой и выводами УФ-лампы / патрона и соедините половинки корпуса.

Демонстрация предложенной методики.

Лампа ультрафиолетовая ESL-PL-9/UVCB/2G7/CL (аналог ДКБУ-9) мощностью 9Вт. Напряжение в лампе 60±6В.

Электронный балласт от лампы Happy Light мощностью 15 Вт. Колба неисправна.

I1 = 15 / 60 = 0,25 A
U1 = U2
I2 = 9 / 60 = 0,15 A
N = 4,67 округляется до 5 витков

Измеренное значение мощности 8,08Вт отличается в меньшую сторону от номинальных 9 Вт, что допустимо, т. к. незначительно влияет на эффективность и не снижает надёжность.

Рисунок 2: Крышка корпуса до доработки
Рисунок 2: Крышка корпуса до доработки

Рисунок 3: Трансформатор обратной связи с домотанной первичной обмоткой.
Рисунок 3: Трансформатор обратной связи с домотанной первичной обмоткой.

Рисунок 4: Тестовое подключение УФ-лампы к балласту.
Рисунок 4: Тестовое подключение УФ-лампы к балласту.

Рисунок 5: Подключение щупов осциллографа.
Рисунок 5: Подключение щупов осциллографа.

Рисунок 6: Осциллограммы тока и напряжения.
Рисунок 6: Осциллограммы тока и напряжения.

Рисунок 7: Осциллограмма мощности.
Рисунок 7: Осциллограмма мощности.

Доработанная крышка корпуса с установленной УФ-лампой
Рисунок 8: Доработанная крышка корпуса с установленной УФ-лампой

image
Рисунок 9: Окончательное подключение УФ-лампы к балласту.

image
Рисунок 10: Готовая лампа.

Работающая лампа
Рисунок 11: Работающая лампа.

ссылка на оригинал статьи https://habr.com/ru/post/495018/

Алгоритмы на экзамене в ШАД

Привет! Меня зовут Александр Курилкин, и я веду курс по алгоритмам в «ШАД Helper». В этом посте я разберу несколько задач из вступительных экзаменов прошлых лет, чтобы вы смогли увидеть, что вас ждет, и понять, чему мы сможем вас научить на нашем курсе. Надеюсь, что вы разделяете мою любовь к интересным задачам по алгоритмам и получите искреннее удовольствие от прочтения этого поста! Итак, приступим…

28.05.2016, №4

Даны $n$ отрезков $[a_i; b_i]$. Назовем индексом вложенности отрезка $[a_i; b_i]$ количество отрезков, которые его содержат. Предложите алгоритм, определяющий, есть ли в наборе отрезок с индексом вложенности, превышающим 1000. Ограничение по времени — $O(n \log n)$, по дополнительной памяти — $O(n)$.

Решение

Воспользуемся методом, который в среде олимпиадников называется "сканлайн". Его суть в том, что мы сводим задачу к набору каких-то событий, которые обрабатываем в отсортированном порядке, что-то при этом делая, таким образом решая задачу. В данном случае для каждого отрезка создадим события "отрезок открылся", которое происходит в момент времени $a_i$, и "отрезок закрылся", которое происходит в момент времени $b_i$. Отсортируем все события по их моменту времени и начнем обрабатывать их в таком порядке. Когда отрезок открывается, нужно увеличить счетчик открытых отрезков на 1. Когда открылся очередной отрезок, а счетчик открытых отрезков уже был больше или равен 1000, казалось бы, мы уже решили задачу — очередной отрезок содержится в 1000 уже открытых ранее. Но все не так просто — какие-то из открытых ранее отрезков могли закрыться раньше, чем закроется наш текущий отрезок, который мы рассматриваем как кандидат на ответ. Чтобы решить эту проблему, давайте поддерживать отсортированное мультимножество (std::multiset в C++), в котором будем хранить координаты концов открытых отрезков. На самом деле, хранить все концы в нем не обязательно — достаточно лишь 1000 максимальных. Теперь, чтобы проверить, что текущий отрезок и правда подходит нам, нужно проверить, что в мультимножестве *set.begin(), то есть минимальный (среди 1000 максимальных) конец не левее конца текущего отрезка. Если это правда, то мы нашли подходящий отрезок и задача решена! Если нет, то увы, придется продолжать обрабатывать события раньше. Если отрезок закрылся, то нужно уменьшить счетчик открытых отрезков и удалить из мультимножества его конец. Если все события обработаны, а подходящий отрезок не обнаружен, то его и нет!

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

Вот и все, задача решена. Мы потратили $O(n \log n)$ времени на сортировку событий, $O(n \log 1000) = O(n)$ времени на операции с мультимножеством концов отрезков и O(n) на все остальное. Таким образом, время работы: $O(n \log n)$. Памяти мы уж точно потратили не больше $O(n)$.

25.05.2019, №4

Дан массив вещественных чисел $A$. Предложите алгоритм, находящий для каждого элемента $A$ индекс ближайшего справа элемента, большего его хотя бы в два раза. Если такого элемента нет, то должно возвращаться значение $None$. Ограничение по времени $O(n \log n)$, по дополнительной памяти — $O(n)$.

Решение

Будем идти по массиву справа налево и поддерживать стек пар $(значение, индекс)$ пройденных чисел. Начнем с самого правого элемента (для него ответ $None$), добавим в стек $(A[n - 1], n - 1)$, пойдем левее. Пусть мы хотим обработать очередное число с индексом $i$. Тогда какие числа, пройденные ранее, не представляют для нас никакого интереса в плане определения ближайшего справа в два раза больше как для нашего числа, так и для чисел левее? Это те числа, которые находятся между нашим и ближайшим большим справа. И правда, зачем они нам в будущем, если у нас уже есть число левее, большее них? А вот ближайшее большее справа нам еще может пригодиться. Поэтому давайте удалять из стека верхние элементы, пока их значения меньше или равны нашему. Как только у верхушки стека значение стало больше, пора остановиться и добавить на вершину стека $(A[i], i)$. Хорошо, со стеком мы поработали, но как теперь определить индекс ближайшего справа в два раза большего числа? Заметим, что числа в стеке отсортированы (сверху вниз) по значению и по расстоянию до $i$. Тогда достаточно двоичным поиском в стеке (да, для этого потребуется индексация стека, поэтому давайте вместо стека использовать std::vector) найти наименьшее значение, большее или равное $A[i] \cdot 2$. Если такое нашлось, нужно взять его индекс (мы же храним его в паре), а если не нашлось, то ответ $None$.

Хорошо, задачу мы решили, но какова асимптотика алгоритма? Каждый элемент добавится и удалится из стека всего один раз, поэтому на все добавления и удаления в стек мы потратим O(n), а еще придется сделать $n$ двоичных поисков, что займет $O(n \log n)$, что все еще укладывается в ограничения задачи. Памяти мы, потратили, очевидно, не больше $O(n)$.

10.06.12, №5
В множестве из $n$ человек каждый может знать или не знать другого (если $A$ знает $B$, отсюда не следует, что $B$ знает $A$). Все знакомства заданы булевой матрицей $n×n$. В этом множестве может найтись или не найтись знаменитость — человек, который никого не знает, но которого знают все. Предложите алгоритм, который бы находил в множестве знаменитость или говорил, что ее в этом множестве нет. Сложность по времени — $O(n)$, сложность по памяти — $O(1)$.

Решение

Для определенности пусть $K_{ij} = 1$, если $i$-й человек знает $j$-го человека, и 0 иначе.

Тогда заметим, что если $K_{ij} = 1 ~ (i \neq j)$, то $i$-й человек точно не может быть знаменитостью, ведь он кого-то знает, а вот $j$-й может, а если $K_{ij} = 0$, то $j$-й человек точно не может быть знаменитостью, ведь $i$-й его не знает.

Это дает нам следующий алгоритм: пусть изначально первый человек — наш кандидат в знаменитости. Давайте начнем идти по первой строке матрицы вправо, пока не встретим 1, скажем, в столбце $l$. Тогда все люди, соответствующие пройденным столбцам (где был записан 0), точно не могут быть знаменитостями, а человек, соответствующий первой строке, тоже не может быть знаменитостью, поскольку он кого-то знает (а именно, человека, соответствующего строке $l$). Тогда $l$-й человек — наш кандидат в знаменитости, и начнем такой же процесс с клетки матрицы $(l, l + 1)$. Если на пути нам встретится 1, то обновим кандидатата, и так далее, пока мы можем идти вправо. Осталось проверить, действительно ли является ли наш последний кандидат знаменитостью. Для этого проверим, что в столбце, соответствующем ему, все единицы (кроме клетки на главной диагонали), это значит, что его все знают, а в его строке все нули, то есть он никого не знает. Пока мы шли по матрице, мы двигались только вправо, а значит, потратили $O(n)$ времени, а во время финальной проверки пробежались по одной строке и по одному столбцу, что также заняло $O(n)$ времени. Итого: $O(n)$ времени. Также ничего, кроме нескольких переменных, мы не хранили (считаем, что матрица была дана заранее и в расчет памяти не входит), и памяти мы заняли $O(1)$. Полное решение!

Это были задачи, требующие смекалки и совсем небольшого знания алгоритмов, а теперь немного алгоритмической жести!

2019, онлайн-контест, задача D

Минимальное остовное дерево, часть 2
Ограничение времени: 2 секунды
Ограничение памяти: 256Mb

Вам дан неориентированный взвешенный граф с $n$ вершинами и $m$ рёбрами. Требуется выполнить $q$ запросов вида «уменьшить вес ребра номер $i$ на 1». После каждого запроса вам нужно вывести вес минимального остовного дерева в графе.

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

Граф связен и не содержит петель и кратных рёбер. Кроме того, гарантируется, что после каждого запроса веса всех рёбер неотрицательны.

Формат ввода

В первой строке входного файла содержатся два целых числа $n$ и $m$ — количество вершин и рёбер графа соответственно ($1 \le n, m \le 10^5, 2 \le n)$. В следующих $m$ строках содержится по три целых числа $u$, $v$, $w$. Это значит, что в графе есть ребро веса $w$, соединяющее вершины $u$ и $v$ ($1 \le u, v \le n, 1 \le w \le 10$).

В следующей строке содержится целое число $q$ — количество запросов ($1 \le q \le 10^5$). В следующих $q$ строках содержится по одному целому числу $id$ ($1 \le id \le m$). Это значит, что требуется уменьшить на единицу вес ребра $id$. Рёбра нумеруются с единицы в порядке следования во входном файле.

Формат вывода

Выведите $q$ чисел, разделённых пробелами или переводами строки — веса минимального остовного дерева после выполнения каждого запроса.

Решение

Для начала давайте поймем, как решать задачу, если бы ограничения были поменьше или наши руки были посильнее 99% поступающих в ШАД. Для начала построим минимальное остовное дерево.

Пускай приходит запрос "уменьшить ребро между $u$ и $v$", и новый вес ребра стал $k$. Тогда если в дереве на пути между $u$ и $v$ есть ребро весом больше $k$ (для поиска такого достаточно найти максимальное ребро на пути. В силу условия задачи оно может быть только весом $k+1$, иначе бы остов не был минимальным, так как на пути в остове между вершинами ребра $u$ и $v$ было ребро веса $> k + 1$, а наше ребро было веса $k + 1$, а значит, выгодно заменить бо́льшее ребро на наше), то нужно удалить его из остова и добавить вместо него наше, уменьшив тем самым вес минимального остовного дерева на 1. Это называется критерий Тарьяна минимальности остовного дерева. Осталось понять, как находить максимальное ребро на пути, удалять ребро из дерева и соединять два дерева ребром. Это можно делать либо наивно за $O(n)$, что не позволит нам решить задачу, уложившись в ограничения по времени, либо за $O(\log n)$ с помощью структуры данных link-cut tree, написание которой у вас займет больше времени, чем сам контест (и это в лучшем случае). Поэтому будем искать другое оптимальное решение.

Так как веса бывают всего лишь от 1 до 10, давайте поддерживать 10 остовов (они могут быть лесами, не обязательно деревьями). В $k$-м остове будут только ребра весом $\le k$. Каждый остов построим алгоритмом Краскала. Для этого есть две причины: он просто пишется, и СНМы (системы непересекающихся множеств) для каждого остова, оставшиеся после него, нам еще понадобятся в будущем. Кроме того, можно заметить, что при построении таким образом первый остов будет подмножеством второго, второй третьего, и так далее.

Пусть вес ребра $(u, v)$ уменьшился на 1 и стал $k$. Попытаемся добавить его в $k$-й остов. Для начала с помощью СНМ проверим, лежат ли в $k$-м остове $u$ и $v$ в одной компоненте связности. Если это не так, то на пути между ними есть ребро веса $k+1$, а значит, можно его выкинуть и заменить на наше. Но делать все это явно не будем, а просто сделаем слияние компонент $u$ и $v$ в СНМе $k$-го остова, заодно уменьшив вес минимального остовного дерева на 1. Если же $u$ и $v$ лежат в одной компоненте связности в $k$-м остове, то между ними есть путь по ребрам с весом $\le k$, и уменьшение веса нашего ребра не принесет никакой пользы.

Задачу решили, пишется не очень сложно, а что там по времени и памяти? $O(\alpha(n))$ на запрос и даже если писать совсем не заботясь о времени, то $O(10 \cdot m \log n) = O(m \log n)$ на препроцессинг. В ограничения должно уложиться, Accepted 🙂

ссылка на оригинал статьи https://habr.com/ru/post/494998/