Merge branch 'master' into 3-parse-rule
Some checks failed
CI fuzzy controller / container-test-job (push) Failing after 52s

# Conflicts:
#	src/main/java/ru/ulstu/fc/rule/controller/RuleController.java
#	src/main/java/ru/ulstu/fc/rule/model/Variable.java
#	src/main/java/ru/ulstu/fc/rule/repository/VariableRepository.java
#	src/main/java/ru/ulstu/fc/rule/service/FuzzyInferenceService.java
#	src/main/java/ru/ulstu/fc/rule/service/VariableService.java
#	src/main/resources/templates/default.html
#	src/main/resources/templates/listRules.html
This commit is contained in:
Anton Romanov 2025-02-24 21:42:35 +04:00
parent 2e0478e9d2
commit 900cfdd036
82 changed files with 2541 additions and 319 deletions

View File

@ -0,0 +1,15 @@
name: CI fuzzy controller
on: [ push ]
jobs:
container-test-job:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21 for x64
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
architecture: x64
- name: Test with Gradle
run: bash ./gradlew test

2
.gitignore vendored
View File

@ -2,6 +2,8 @@
# Created by https://www.toptal.com/developers/gitignore/api/intellij,java,maven,gradle,eclipse,netbeans,node # Created by https://www.toptal.com/developers/gitignore/api/intellij,java,maven,gradle,eclipse,netbeans,node
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij,java,maven,gradle,eclipse,netbeans,node # Edit at https://www.toptal.com/developers/gitignore?templates=intellij,java,maven,gradle,eclipse,netbeans,node
data/*
### Eclipse ### ### Eclipse ###
.metadata .metadata
bin/ bin/

12
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "FuzzyControllerApplication",
"request": "launch",
"mainClass": "ru.ulstu.fc.FuzzyControllerApplication",
"projectName": "fuzzy-controller"
}
]
}

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"java.configuration.updateBuildConfiguration": "interactive",
"java.compile.nullAnalysis.mode": "disabled"
}

31
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,31 @@
pipeline {
agent any
tools {
jdk 'jdk-21'
}
stages {
stage('Build') {
steps {
script {
sh "sh ./gradlew clean build"
}
}
}
stage('Deploy') {
steps {
script {
sh "bash deploy/nio17.sh &"
}
}
}
}
post {
always {
script {
if (currentBuild.currentResult == 'FAILURE') {
step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: "a.romanov@ulstu.ru", sendToIndividuals: true])
}
}
}
}
}

View File

@ -1,2 +1,24 @@
# fuzzy-controller # Нечеткий контроллер
Порядок развертывания проекта разработчиками:
1. Скачать и установить JDK (используется 21 LTS
JDK): https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe
2. Проверить установку, набрав в консоли команду "java -version"
3. Скачать и установить IDE: IntelliJIDEA Сommunity
version https://www.jetbrains.com/ru-ru/idea/download/#section=windows
ИЛИ
Visual Studio Code https://code.visualstudio.com/
4. Скачать и открыть проект в IDE:
![open.png](open.png)
5. Сформировать конфигурацию для запуска:
![run.png](run.png)
6. Запустить проект.
7. Открыть страницу в браузере http://localhost:8080/
8. Демо: http://plans.athene.tech/

78
client example.py Normal file
View File

@ -0,0 +1,78 @@
import json
import requests
url = 'http://plans.athene.tech/rest/get-inference'
headers = {
'Content-type': 'application/json',
'Accept': 'application/json'
}
age = 65
income = 20000
data = {
"inputVariables": [
{
"name": "возраст",
"values": [
{
"fuzzyTerm": "молодой",
"value": 35
},
{
"fuzzyTerm": "средний",
"value": 45
},
{
"fuzzyTerm": "старый",
"value": 65
}]
},
{
"name": "доход",
"values": [
{
"fuzzyTerm": "низкий",
"value": 50000
},
{
"fuzzyTerm": "средний",
"value": 100000
},
{
"fuzzyTerm": "высокий",
"value": 500000
}]
}],
"outputVariable":
{
"name": "кредит",
"values": [
{
"fuzzyTerm": "небольшой",
"value": 50000
},
{
"fuzzyTerm": "средний",
"value": 100000
},
{
"fuzzyTerm": "большой",
"value": 200000
}]
},
"rules": [
"if доход is высокий and возраст is молодой then кредит is большой",
"if доход is высокий and возраст is средний then кредит is средний",
"if доход is высокий and возраст is старый then кредит is средний",
"if доход is низкий and возраст is молодой then кредит is небольшой"
],
"values":
{
"доход": income,
"возраст": age
}
}
response = requests.post(url, data=json.dumps(data), headers=headers)
print(response.json())

4
deploy/nio17.sh Normal file
View File

@ -0,0 +1,4 @@
#!/bin/
bash ./gradlew assemble
scp -o StrictHostKeyChecking=no build/libs/fuzzy-controller-0.0.1-SNAPSHOT.jar root@192.168.1.129:/root/fuzzy-controller.jar
ssh root@192.168.1.129 "killall java >> /dev/null && /opt/jdk-21/bin/java -jar /root/fuzzy-controller.jar >> /root/fc.log &"

Binary file not shown.

View File

@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

BIN
open.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
run.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -2,11 +2,28 @@ package ru.ulstu.fc;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.event.EventListener;
import ru.ulstu.fc.user.service.UserService;
@SpringBootApplication @SpringBootApplication
@EnableConfigurationProperties
public class FuzzyControllerApplication { public class FuzzyControllerApplication {
private final UserService userService;
public FuzzyControllerApplication(UserService userService) {
this.userService = userService;
}
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(FuzzyControllerApplication.class, args); SpringApplication.run(FuzzyControllerApplication.class, args);
} }
@EventListener(ApplicationReadyEvent.class)
public void doSomethingAfterStartup() {
System.out.println("hello world, I have just started up");
userService.initDefaultAdmin();
}
} }

View File

@ -0,0 +1,12 @@
package ru.ulstu.fc.config;
public class Constants {
public static final int MIN_PASSWORD_LENGTH = 6;
public static final String LOGIN_REGEX = "^[_'.@A-Za-z0-9-]*$";
public static final String COOKIES_NAME = "JSESSIONID";
public static final String LOGOUT_URL = "/login?logout";
public static final String SESSION_ID_ATTR = "sessionId";
public static final int SESSION_TIMEOUT_SECONDS = 30 * 60;
}

View File

@ -1,5 +1,6 @@
package ru.ulstu.fc.config; package ru.ulstu.fc.config;
import io.swagger.v3.oas.annotations.Hidden;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -7,25 +8,13 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.NoHandlerFoundException;
import springfox.documentation.annotations.ApiIgnore;
@ControllerAdvice @ControllerAdvice
@ApiIgnore @Hidden
class GlobalDefaultExceptionHandler { class GlobalDefaultExceptionHandler {
private final static Logger LOG = LoggerFactory.getLogger(GlobalDefaultExceptionHandler.class); private final static Logger LOG = LoggerFactory.getLogger(GlobalDefaultExceptionHandler.class);
public static final String DEFAULT_ERROR_VIEW = "error"; public static final String DEFAULT_ERROR_VIEW = "error";
// @ExceptionHandler(value = Exception.class)
// public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) {
// LOG.warn(e.getMessage());
// ModelAndView mav = new ModelAndView();
// mav.addObject("exception", e);
// mav.addObject("url", req.getRequestURL());
// mav.setViewName(DEFAULT_ERROR_VIEW);
// return mav;
// }
@ExceptionHandler(NoHandlerFoundException.class) @ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND) @ResponseStatus(HttpStatus.NOT_FOUND)
public String handle(NoHandlerFoundException ex) { public String handle(NoHandlerFoundException ex) {

View File

@ -1,9 +1,3 @@
/*
* Copyright (C) 2021 Anton Romanov - All Rights Reserved
* You may use, distribute and modify this code, please write to: romanov73@gmail.com.
*
*/
package ru.ulstu.fc.config; package ru.ulstu.fc.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -11,19 +5,19 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver; import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration @Configuration
public class MvcConfiguration implements WebMvcConfigurer { public class MvcConfiguration implements WebMvcConfigurer {
// @Override @Override
// public void addViewControllers(ViewControllerRegistry registry) { public void addViewControllers(ViewControllerRegistry registry) {
// registry.addViewController("/index"); registry.addViewController("/login");
// } registry.addViewController("/loginError");
registry.addViewController("/index");
}
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
@ -44,14 +38,6 @@ public class MvcConfiguration implements WebMvcConfigurer {
return localeInterceptor; return localeInterceptor;
} }
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("ru.ulstu"))
.build();
}
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeInterceptor()); registry.addInterceptor(localeInterceptor());

View File

@ -0,0 +1,13 @@
package ru.ulstu.fc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class PasswordEncoderConfiguration {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,51 @@
package ru.ulstu.fc.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import ru.ulstu.fc.user.model.UserRoleConstants;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
private final String[] permittedUrls = new String[]{
"/login", "/index", "/user/register",
"/public/**", "/organizers", "/webjars/**",
"/error", "/register",
"/h2-console/*", "/h2-console",
"/css/**", "/js/**", "/img/**",
"/templates/**", "/webjars/**"};
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
log.debug("Security enabled");
http
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth ->
auth.requestMatchers("/").permitAll()
.requestMatchers(permittedUrls).permitAll()
.requestMatchers("/swagger-ui.html").hasAuthority(UserRoleConstants.ADMIN)
.anyRequest().authenticated())
.formLogin(form ->
form.loginPage("/login")
.failureUrl("/loginError")
.permitAll())
.logout(logout ->
logout
.logoutSuccessUrl(Constants.LOGOUT_URL)
.invalidateHttpSession(false)
.clearAuthentication(true)
.deleteCookies(Constants.COOKIES_NAME)
.permitAll());
return http.build();
}
}

View File

@ -1,19 +1,21 @@
package ru.ulstu.fc.config; package ru.ulstu.fc.config;
import nz.net.ultraq.thymeleaf.LayoutDialect; import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.thymeleaf.spring5.SpringTemplateEngine; import org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.ITemplateResolver;
@Configuration @Configuration
public class TemplateConfiguration { public class TemplateConfiguration {
@Bean @Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) { public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver, SpringSecurityDialect sec) {
final SpringTemplateEngine templateEngine = new SpringTemplateEngine(); final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(templateResolver); templateEngine.addTemplateResolver(templateResolver);
templateEngine.addDialect(new LayoutDialect()); templateEngine.addDialect(new LayoutDialect());
templateEngine.addDialect(sec);
return templateEngine; return templateEngine;
} }
} }

View File

@ -1,13 +0,0 @@
package ru.ulstu.fc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class WebClientConfiguration {
@Bean
public WebClient webClient(WebClient.Builder webClientBuilder) {
return webClientBuilder.build();
}
}

View File

@ -0,0 +1,84 @@
package ru.ulstu.fc.core.model;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.Version;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
@MappedSuperclass
public abstract class BaseEntity implements Serializable, Comparable<BaseEntity> {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
@Version
private Integer version;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!getClass().isAssignableFrom(obj.getClass())) {
return false;
}
BaseEntity other = (BaseEntity) obj;
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (id == null ? 0 : id.hashCode());
return result;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"id=" + id +
", version=" + version +
'}';
}
@Override
public int compareTo(@NotNull BaseEntity o) {
return id != null ? id.compareTo(o.getId()) : -1;
}
public void reset() {
this.id = null;
this.version = null;
}
}

View File

@ -0,0 +1,119 @@
package ru.ulstu.fc.core.model;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.io.Serializable;
import java.util.Optional;
public class OffsetablePageRequest implements Pageable, Serializable {
private final int offset;
private final int count;
private final Sort sort;
public OffsetablePageRequest(int page, long pageSize) {
this(pageSize * page, pageSize, Sort.by("id"));
}
public OffsetablePageRequest(long offset, long count, Sort sort) {
if (offset < 0) {
throw new IllegalArgumentException("Offset value must not be less than zero!");
}
if (count < 1) {
throw new IllegalArgumentException("Count value must not be less than one!");
}
this.offset = (int) offset;
this.count = (int) count;
this.sort = sort;
}
@Override
public Sort getSort() {
return sort;
}
@Override
public Sort getSortOr(Sort sort) {
return Pageable.super.getSortOr(sort);
}
@Override
public int getPageSize() {
return count;
}
@Override
public boolean isPaged() {
return Pageable.super.isPaged();
}
@Override
public boolean isUnpaged() {
return Pageable.super.isUnpaged();
}
@Override
public int getPageNumber() {
return offset / count;
}
@Override
public long getOffset() {
return offset;
}
@Override
public boolean hasPrevious() {
return offset > 0;
}
@Override
public Optional<Pageable> toOptional() {
return Pageable.super.toOptional();
}
@Override
public Pageable next() {
return new OffsetablePageRequest(getOffset() + getPageSize(), getPageSize(), getSort());
}
@Override
public Pageable previousOrFirst() {
return hasPrevious() ? previous() : first();
}
public Pageable previous() {
return getOffset() == 0 ? this : new OffsetablePageRequest(getOffset() - getPageSize(), getPageSize(), getSort());
}
@Override
public Pageable first() {
return new OffsetablePageRequest(0, getPageSize(), getSort());
}
@Override
public Pageable withPage(int pageNumber) {
return null;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final OffsetablePageRequest other = (OffsetablePageRequest) obj;
return this.offset == other.offset && this.count == other.count;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + offset;
result = prime * result + count;
return result;
}
}

View File

@ -0,0 +1,66 @@
package ru.ulstu.fc.project.controller;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import ru.ulstu.fc.project.model.Project;
import ru.ulstu.fc.project.model.ProjectForm;
import ru.ulstu.fc.project.service.ProjectRulesService;
import ru.ulstu.fc.project.service.ProjectService;
import ru.ulstu.fc.project.service.ProjectVariableService;
import ru.ulstu.fc.user.model.UserRoleConstants;
@Controller
@Hidden
@RequestMapping("project")
@Secured({UserRoleConstants.ADMIN})
public class ProjectController {
private final ProjectService projectService;
private final ProjectRulesService projectRulesService;
private final ProjectVariableService projectVariableService;
public ProjectController(ProjectService projectService,
ProjectRulesService projectRulesService,
ProjectVariableService projectVariableService) {
this.projectService = projectService;
this.projectRulesService = projectRulesService;
this.projectVariableService = projectVariableService;
}
@GetMapping("list")
public String getProjects(Model model) {
model.addAttribute("projects", projectService.getCurrentUserProjects());
return "project/list";
}
@GetMapping("/edit/{projectId}")
public String edit(@PathVariable(value = "projectId") Integer id, Model model) {
model.addAttribute("project",
new ProjectForm((id != null && id != 0)
? projectService.getById(id)
: new Project()));
model.addAttribute("rules", projectRulesService.getByProjectId(id));
model.addAttribute("variables", projectVariableService.getByProjectId(id));
return "project/edit";
}
@PostMapping(value = "save", params = "save")
public String save(ProjectForm projectForm, Model model) {
model.addAttribute("project", projectService.save(projectForm));
return "redirect:/project/list";
}
@PostMapping(value = "save", params = "delete")
public String delete(ProjectForm projectForm) {
if (projectForm != null && projectForm.getId() != null) {
projectService.delete(projectForm);
}
return "redirect:/project/list";
}
}

View File

@ -0,0 +1,41 @@
package ru.ulstu.fc.project.controller;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.ulstu.fc.project.model.Project;
import ru.ulstu.fc.project.model.ProjectForm;
import ru.ulstu.fc.project.service.ProjectService;
import ru.ulstu.fc.user.model.UserRoleConstants;
import java.util.List;
@RestController
@RequestMapping("projectRest")
@Secured({UserRoleConstants.ADMIN})
public class ProjectRestController {
private final ProjectService projectService;
public ProjectRestController(ProjectService projectService) {
this.projectService = projectService;
}
@GetMapping("list")
public List<Project> getProjects() {
return projectService.getCurrentUserProjects();
}
@PostMapping("save")
public Project save(Project project) {
return projectService.save(project);
}
@DeleteMapping("delete")
public String delete(ProjectForm projectForm) {
projectService.delete(projectForm);
return "redirect:/list";
}
}

View File

@ -0,0 +1,53 @@
package ru.ulstu.fc.project.model;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotEmpty;
import ru.ulstu.fc.core.model.BaseEntity;
import ru.ulstu.fc.user.model.User;
import java.util.Date;
@Entity
public class Project extends BaseEntity {
@NotEmpty(message = "Текст новости не может быть пустым")
private String name;
private Date createDate = new Date();
@ManyToOne(cascade = CascadeType.MERGE)
private User user;
public Project() {
}
public Project(ProjectForm projectForm) {
if (projectForm.getId() != null) {
setId(projectForm.getId());
}
this.name = projectForm.getName();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}

View File

@ -0,0 +1,38 @@
package ru.ulstu.fc.project.model;
import java.util.Date;
public class ProjectForm {
private Integer id;
private String name;
private Date createDate;
public ProjectForm() {
}
public ProjectForm(Project project) {
this.id = project.getId();
this.name = project.getName();
this.createDate = project.getCreateDate();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateDate() {
return createDate;
}
}

View File

@ -0,0 +1,14 @@
package ru.ulstu.fc.project.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import ru.ulstu.fc.project.model.Project;
import java.util.List;
@Repository
public interface ProjectRepository extends JpaRepository<Project, Integer> {
List<Project> findAllByUserId(@Param("userId") Integer userId);
}

View File

@ -0,0 +1,27 @@
package ru.ulstu.fc.project.service;
import org.springframework.stereotype.Service;
import ru.ulstu.fc.rule.model.FuzzyRule;
import ru.ulstu.fc.rule.repository.FuzzyRuleRepository;
import java.util.Collections;
import java.util.List;
@Service
public class ProjectRulesService {
private final FuzzyRuleRepository ruleRepository;
private final ProjectService projectService;
public ProjectRulesService(FuzzyRuleRepository ruleRepository,
ProjectService projectService) {
this.ruleRepository = ruleRepository;
this.projectService = projectService;
}
public List<FuzzyRule> getByProjectId(Integer projectId) {
if (projectId == null || projectId == 0) {
return Collections.emptyList();
}
return ruleRepository.findByProject(projectService.getById(projectId));
}
}

View File

@ -0,0 +1,64 @@
package ru.ulstu.fc.project.service;
import org.springframework.stereotype.Service;
import ru.ulstu.fc.project.model.Project;
import ru.ulstu.fc.project.model.ProjectForm;
import ru.ulstu.fc.project.repository.ProjectRepository;
import ru.ulstu.fc.user.model.User;
import ru.ulstu.fc.user.service.UserService;
import java.util.List;
@Service
public class ProjectService {
private final ProjectRepository projectRepository;
private final UserService userService;
public ProjectService(ProjectRepository projectRepository,
UserService userService) {
this.projectRepository = projectRepository;
this.userService = userService;
}
public List<Project> getCurrentUserProjects() {
return projectRepository.findAllByUserId(userService.getCurrentUser().getId());
}
public Project getById(Integer id) {
Project project = projectRepository
.findById(id)
.orElseThrow(() -> new RuntimeException("Project not found by id"));
checkUserProjectWithThrow(project, userService.getCurrentUser());
return project;
}
public Project save(ProjectForm projectForm) {
return save(new Project(projectForm));
}
public Project save(Project projectToSave) {
User currentUser = userService.getCurrentUser();
if (projectToSave.getId() == null) {
projectToSave.setUser(currentUser);
return projectRepository.save(projectToSave);
}
Project dbProject = getById(projectToSave.getId());
dbProject.setName(projectToSave.getName());
return projectRepository.save(dbProject);
}
public void delete(ProjectForm projectForm) {
getById(projectForm.getId());
projectRepository.deleteById(projectForm.getId());
}
private void checkUserProjectWithThrow(Project project, User currentUser) {
if (!isUserProject(project, currentUser)) {
throw new RuntimeException("User can not get access to project");
}
}
private boolean isUserProject(Project project, User currentUser) {
return (currentUser.equals(project.getUser()));
}
}

View File

@ -0,0 +1,27 @@
package ru.ulstu.fc.project.service;
import org.springframework.stereotype.Service;
import ru.ulstu.fc.rule.model.Variable;
import ru.ulstu.fc.rule.repository.VariableRepository;
import java.util.Collections;
import java.util.List;
@Service
public class ProjectVariableService {
private final VariableRepository variableRepository;
private final ProjectService projectService;
public ProjectVariableService(VariableRepository variableRepository,
ProjectService projectService) {
this.variableRepository = variableRepository;
this.projectService = projectService;
}
public List<Variable> getByProjectId(Integer projectId) {
if (projectId == null || projectId == 0) {
return Collections.emptyList();
}
return variableRepository.findByProject(projectService.getById(projectId));
}
}

View File

@ -0,0 +1,54 @@
package ru.ulstu.fc.rule.controller;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import ru.ulstu.fc.rule.model.FuzzyTermForm;
import ru.ulstu.fc.rule.service.FuzzyTermService;
@Controller
@RequestMapping("fuzzyTerm")
public class FuzzyTermController {
private final FuzzyTermService fuzzyTermService;
public FuzzyTermController(FuzzyTermService fuzzyTermService) {
this.fuzzyTermService = fuzzyTermService;
}
@GetMapping("/edit/{projectId}/{variableId}/{fuzzyTermId}")
public String edit(@PathVariable(value = "projectId") Integer projectId,
@PathVariable(value = "variableId") Integer variableId,
@PathVariable(value = "fuzzyTermId") Integer fuzzyTermId, Model model) {
model.addAttribute("projectId", projectId);
model.addAttribute("variableId", variableId);
model.addAttribute("fuzzyTermForm",
(fuzzyTermId == null || fuzzyTermId == 0)
? new FuzzyTermForm(fuzzyTermId, projectId, variableId)
: new FuzzyTermForm(fuzzyTermId, projectId, variableId, fuzzyTermService.getById(fuzzyTermId)));
model.addAttribute("fuzzyTerms", fuzzyTermService.getByVariableId(variableId));
return "fuzzyTerm/edit";
}
@PostMapping(value = "save", params = "save")
public String save(@Valid FuzzyTermForm fuzzyTermForm, BindingResult result, Model model) {
if (result.hasErrors()) {
model.addAttribute("projectId", fuzzyTermForm.getProjectId());
return "fuzzyTerm/edit";
}
fuzzyTermService.save(fuzzyTermForm);
return "redirect:/variable/edit/" + fuzzyTermForm.getProjectId() + "/" + fuzzyTermForm.getVariableId();
}
@PostMapping(value = "save", params = "delete")
public String delete(FuzzyTermForm fuzzyTermForm) {
if (fuzzyTermForm != null && fuzzyTermForm.getId() != null) {
fuzzyTermService.delete(fuzzyTermForm);
}
return "redirect:/variable/edit/" + fuzzyTermForm.getProjectId() + "/" + fuzzyTermForm.getVariableId();
}
}

View File

@ -1,22 +1,23 @@
package ru.ulstu.fc.rule.controller; package ru.ulstu.fc.rule.controller;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import ru.ulstu.fc.rule.model.Antecedent; import ru.ulstu.fc.rule.model.FuzzyTerm;
import ru.ulstu.fc.rule.model.InferenceForm; import ru.ulstu.fc.rule.model.InferenceForm;
import ru.ulstu.fc.rule.model.Variable;
import ru.ulstu.fc.rule.service.FuzzyInferenceService; import ru.ulstu.fc.rule.service.FuzzyInferenceService;
import springfox.documentation.annotations.ApiIgnore;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@Controller @Controller
@ApiIgnore @Hidden
public class InferenceMvcController { public class InferenceMvcController {
private final FuzzyInferenceService fuzzyInferenceService; private final FuzzyInferenceService fuzzyInferenceService;
@ -26,35 +27,38 @@ public class InferenceMvcController {
@GetMapping("/") @GetMapping("/")
public String initInference(Model model) { public String initInference(Model model) {
model.addAttribute("ageAntecedents", getAgeAntecedents()); model.addAttribute("ageValues", getAgeValues());
model.addAttribute("incomeAntecedents", getIncomeAntecedents()); model.addAttribute("incomeValues", getIncomeValues());
model.addAttribute("inferenceForm", new InferenceForm()); model.addAttribute("inferenceForm", new InferenceForm());
return "index"; return "index";
} }
@RequestMapping(value = "get-inference", method = RequestMethod.POST) @RequestMapping(value = "get-inference", method = RequestMethod.POST)
public String getInference(@ModelAttribute InferenceForm inferenceForm, Model model) { public String getInference(@ModelAttribute InferenceForm inferenceForm, Model model) {
model.addAttribute("ageAntecedents", getAgeAntecedents()); model.addAttribute("ageValues", getAgeValues());
model.addAttribute("incomeAntecedents", getIncomeAntecedents()); model.addAttribute("incomeValues", getIncomeValues());
model.addAttribute("inferenceForm", inferenceForm); model.addAttribute("inferenceForm", inferenceForm);
model.addAttribute("response", fuzzyInferenceService.getFuzzyInference( model.addAttribute("response", fuzzyInferenceService.getFuzzyInference(
Map.of("возраст", Double.valueOf(inferenceForm.getAgeAntecedent()), Map.of("возраст", Double.valueOf(inferenceForm.getAgeValue()),
"доход", Double.valueOf(inferenceForm.getIncomeAntecedent()) "доход", Double.valueOf(inferenceForm.getIncomeValue()))));
)));
return "index"; return "index";
} }
private List<Antecedent> getAgeAntecedents() { private List<FuzzyTerm> getAgeValues() {
return Arrays.asList( Variable var = new Variable("Age");
new Antecedent("молодой", "30"), var.getFuzzyTerms().addAll(Arrays.asList(
new Antecedent("средний", "45"), new FuzzyTerm("молодой", 30.0),
new Antecedent("старый", "60")); new FuzzyTerm("средний", 45.0),
new FuzzyTerm("старый", 60.0)));
return var.getFuzzyTerms();
} }
private List<Antecedent> getIncomeAntecedents() { private List<FuzzyTerm> getIncomeValues() {
return Arrays.asList( Variable var = new Variable("Income");
new Antecedent("небольшой", "20000"), var.getFuzzyTerms().addAll(Arrays.asList(
new Antecedent("средний", "90000"), new FuzzyTerm("небольшой", 20000.0),
new Antecedent("высокий", "200000")); new FuzzyTerm("средний", 90000.0),
new FuzzyTerm("высокий", 200000.0)));
return var.getFuzzyTerms();
} }
} }

View File

@ -11,7 +11,7 @@ import ru.ulstu.fc.rule.service.FuzzyInferenceService;
import java.util.List; import java.util.List;
@RestController @RestController
@RequestMapping("rest") @RequestMapping("inferenceRest")
public class InferenceRestController { public class InferenceRestController {
private final FuzzyInferenceService fuzzyInferenceService; private final FuzzyInferenceService fuzzyInferenceService;

View File

@ -1,96 +1,51 @@
package ru.ulstu.fc.rule.controller; package ru.ulstu.fc.rule.controller;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestMapping;
import ru.ulstu.fc.rule.model.AddRuleForm; import ru.ulstu.fc.rule.model.FuzzyRuleForm;
import ru.ulstu.fc.rule.model.AddTermForm; import ru.ulstu.fc.rule.service.FuzzyRuleService;
import ru.ulstu.fc.rule.model.AddVariableForm;
import ru.ulstu.fc.rule.service.RuleParseService;
import ru.ulstu.fc.rule.service.RuleService;
import ru.ulstu.fc.rule.service.TermsService;
import ru.ulstu.fc.rule.service.VariableService;
import java.util.List;
@Controller @Controller
@RequestMapping("rule")
public class RuleController { public class RuleController {
private final RuleParseService ruleParseService; private final FuzzyRuleService ruleService;
private final RuleService ruleService;
private final TermsService termService;
private final VariableService variableService;
public RuleController(RuleParseService ruleParseService, public RuleController(FuzzyRuleService ruleService) {
RuleService ruleService,
TermsService termService,
VariableService variableService) {
this.ruleParseService = ruleParseService;
this.ruleService = ruleService; this.ruleService = ruleService;
this.termService = termService;
this.variableService = variableService;
} }
@GetMapping("listRules") @GetMapping("/edit/{projectId}/{ruleId}")
public String listRules(Model model) { public String edit(@PathVariable(value = "projectId") Integer projectId,
model.addAttribute("rules", ruleService.getRules()); @PathVariable(value = "ruleId") Integer id, Model model) {
return "listRules"; model.addAttribute("projectId", projectId);
model.addAttribute("fuzzyRuleForm",
(id != null && id != 0)
? new FuzzyRuleForm(id, ruleService.getById(id))
: new FuzzyRuleForm(id, projectId));
return "rule/edit";
} }
@GetMapping("listTerms") @PostMapping(value = "save", params = "save")
public String listTerms(Model model) { public String save(@Valid FuzzyRuleForm fuzzyRuleForm, BindingResult bindingResult, Model model) {
model.addAttribute("terms", termService.getTerms()); if (bindingResult.hasErrors()) {
return "listTerms"; model.addAttribute("projectId", fuzzyRuleForm.getProjectId());
return "rule/edit";
}
ruleService.save(fuzzyRuleForm);
return "redirect:/project/edit/" + fuzzyRuleForm.getProjectId();
} }
@GetMapping("listVars") @PostMapping(value = "save", params = "delete")
public String listVariables(Model model) { public String delete(FuzzyRuleForm fuzzyRuleForm) {
model.addAttribute("vars", variableService.getVars()); if (fuzzyRuleForm != null && fuzzyRuleForm.getId() != null) {
return "listVars"; ruleService.delete(fuzzyRuleForm);
} }
return "redirect:/project/edit/" + fuzzyRuleForm.getProjectId();
@GetMapping("addRule")
public String addRule(Model model) {
model.addAttribute("addRuleForm", new AddRuleForm());
return "addRule";
}
@PostMapping("addRule")
public String parse(@ModelAttribute AddRuleForm addRuleForm, Model model) {
try {
System.out.println(ruleParseService.parseRules(List.of(addRuleForm.getRule())));
} catch (Exception ex) {
return "addRule";
}
model.addAttribute("addRuleForm", addRuleForm);
return "listRules";
}
@GetMapping("addVariable")
public String addVariable(Model model, @RequestParam(required = false) Integer id) {
model.addAttribute("addVariableForm", variableService.getAddVariableFormOrDefault(id));
return "addVariable";
}
@PostMapping("addVariable")
public String addVariable(@ModelAttribute AddVariableForm addVariableForm, Model model) {
model.addAttribute("addVariableForm", addVariableForm);
variableService.save(addVariableForm);
model.addAttribute("vars", variableService.getVars());
return "listVars";
}
@GetMapping("addTerm")
public String addTerm(Model model) {
model.addAttribute("addTermForm", new AddTermForm());
return "addTerm";
}
@PostMapping("addTerm")
public String addTerm(@ModelAttribute AddTermForm addTermForm, Model model) {
model.addAttribute("addTermForm", addTermForm);
return "listTerms";
} }
} }

View File

@ -0,0 +1,56 @@
package ru.ulstu.fc.rule.controller;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import ru.ulstu.fc.rule.model.VariableForm;
import ru.ulstu.fc.rule.service.FuzzyTermService;
import ru.ulstu.fc.rule.service.VariableService;
@Controller
@RequestMapping("variable")
public class VariableController {
private final VariableService variableService;
private final FuzzyTermService termService;
public VariableController(VariableService variableService,
FuzzyTermService termService) {
this.variableService = variableService;
this.termService = termService;
}
@GetMapping("/edit/{projectId}/{variableId}")
public String edit(@PathVariable(value = "projectId") Integer projectId,
@PathVariable(value = "variableId") Integer variableId, Model model) {
model.addAttribute("projectId", projectId);
model.addAttribute("variableForm",
(variableId == null || variableId == 0)
? new VariableForm(variableId, projectId)
: new VariableForm(variableId, variableService.getById(variableId)));
model.addAttribute("fuzzyTerms", termService.getByVariableId(variableId));
return "variable/edit";
}
@PostMapping(value = "save", params = "save")
public String save(@Valid VariableForm variableForm, BindingResult result, Model model) {
if (result.hasErrors()) {
model.addAttribute("projectId", variableForm.getProjectId());
return "variable/edit";
}
variableService.save(variableForm);
return "redirect:/project/edit/" + variableForm.getProjectId();
}
@PostMapping(value = "save", params = "delete")
public String delete(VariableForm variableForm) {
if (variableForm != null && variableForm.getId() != null) {
variableService.delete(variableForm);
}
return "redirect:/project/edit/" + variableForm.getProjectId();
}
}

View File

@ -1,19 +0,0 @@
package ru.ulstu.fc.rule.model;
public class Antecedent {
private String value;
private String description;
public Antecedent(String description, String value) {
this.description = description;
this.value = value;
}
public String getValue() {
return value;
}
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,36 @@
package ru.ulstu.fc.rule.model;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import ru.ulstu.fc.core.model.BaseEntity;
import ru.ulstu.fc.project.model.Project;
@Entity
public class FuzzyRule extends BaseEntity {
@ManyToOne
@NotNull
private Project project;
@Size(min = 5, max = 250, message = "Длина от 5 до 250 символов")
private String content;
public FuzzyRule() {
}
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

View File

@ -0,0 +1,52 @@
package ru.ulstu.fc.rule.model;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class FuzzyRuleForm {
private Integer id;
@NotNull
private Integer projectId;
@Size(min = 5, max = 250, message = "Длина от 5 до 250 символов")
private String content;
public FuzzyRuleForm() {
}
public FuzzyRuleForm(Integer id, Integer projectId) {
this.id = id;
this.projectId = projectId;
}
public FuzzyRuleForm(Integer id, FuzzyRule fuzzyRule) {
this.id = fuzzyRule.getId();
this.projectId = fuzzyRule.getProject().getId();
this.content = fuzzyRule.getContent();
}
public Integer getProjectId() {
return projectId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setProjectId(Integer projectId) {
this.projectId = projectId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

View File

@ -0,0 +1,34 @@
package ru.ulstu.fc.rule.model;
import jakarta.persistence.Entity;
import ru.ulstu.fc.core.model.BaseEntity;
@Entity
public class FuzzyTerm extends BaseEntity {
private String description;
private Double crispValue;
public FuzzyTerm() {
}
public FuzzyTerm(String description, Double value) {
this.description = description;
this.crispValue = value;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getCrispValue() {
return crispValue;
}
public void setCrispValue(Double crispValue) {
this.crispValue = crispValue;
}
}

View File

@ -0,0 +1,71 @@
package ru.ulstu.fc.rule.model;
public class FuzzyTermForm {
private Integer projectId;
private Integer variableId;
private Integer id;
private String description;
private Double crispValue;
public FuzzyTermForm() {
}
public FuzzyTermForm(String description, Double value) {
this.description = description;
this.crispValue = value;
}
public FuzzyTermForm(Integer id, Integer projectId, Integer variableId) {
this.projectId = projectId;
this.variableId = variableId;
this.id = id;
}
public FuzzyTermForm(Integer id, Integer projectId, Integer variableId, FuzzyTerm fuzzyTerm) {
this.id = id;
this.projectId = projectId;
this.variableId = variableId;
this.crispValue = fuzzyTerm.getCrispValue();
this.description = fuzzyTerm.getDescription();
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getCrispValue() {
return crispValue;
}
public void setCrispValue(Double crispValue) {
this.crispValue = crispValue;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getProjectId() {
return projectId;
}
public void setProjectId(Integer projectId) {
this.projectId = projectId;
}
public Integer getVariableId() {
return variableId;
}
public void setVariableId(Integer variableId) {
this.variableId = variableId;
}
}

View File

@ -1,22 +1,22 @@
package ru.ulstu.fc.rule.model; package ru.ulstu.fc.rule.model;
public class InferenceForm { public class InferenceForm {
private String ageAntecedent; private String ageValue;
private String incomeAntecedent; private String incomeValue;
public String getAgeAntecedent() { public String getAgeValue() {
return ageAntecedent; return ageValue;
} }
public void setAgeAntecedent(String ageAntecedent) { public void setAgeValue(String ageAntecedent) {
this.ageAntecedent = ageAntecedent; this.ageValue = ageAntecedent;
} }
public String getIncomeAntecedent() { public String getIncomeValue() {
return incomeAntecedent; return incomeValue;
} }
public void setIncomeAntecedent(String incomeAntecedent) { public void setIncomeValue(String incomeAntecedent) {
this.incomeAntecedent = incomeAntecedent; this.incomeValue = incomeAntecedent;
} }
} }

View File

@ -1,16 +1,33 @@
package ru.ulstu.fc.rule.model; package ru.ulstu.fc.rule.model;
import ru.ulstu.fc.core.BaseEntity; import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import ru.ulstu.fc.core.model.BaseEntity;
import ru.ulstu.fc.project.model.Project;
import javax.persistence.Entity; import java.util.ArrayList;
import javax.persistence.OneToMany;
import java.util.List; import java.util.List;
@Entity @Entity
public class Variable extends BaseEntity { public class Variable extends BaseEntity {
@Size(min = 3, max = 250, message = "Длина должна быть от 3 до 250")
private String name; private String name;
@OneToMany
private List<Term> terms; @ManyToOne
@NotNull
private Project project;
private boolean input = true;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "variable_id")
private List<FuzzyTerm> fuzzyTerms = new ArrayList<>();
public Variable() { public Variable() {
} }
@ -19,9 +36,9 @@ public class Variable extends BaseEntity {
this.name = name; this.name = name;
} }
public Variable(String name, List<Term> terms) { public Variable(String name, List<FuzzyTerm> fuzzyTerms) {
this.name = name; this.name = name;
this.terms = terms; this.fuzzyTerms = fuzzyTerms;
} }
public String getName() { public String getName() {
@ -32,11 +49,27 @@ public class Variable extends BaseEntity {
this.name = name; this.name = name;
} }
public List<Term> getTerms() { public List<FuzzyTerm> getFuzzyTerms() {
return terms; return fuzzyTerms;
} }
public void setTerms(List<Term> terms) { public void setFuzzyTerms(List<FuzzyTerm> fuzzyTerms) {
this.terms = terms; this.fuzzyTerms = fuzzyTerms;
}
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}
public boolean isInput() {
return input;
}
public void setInput(boolean input) {
this.input = input;
} }
} }

View File

@ -0,0 +1,61 @@
package ru.ulstu.fc.rule.model;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class VariableForm {
private Integer id;
@NotNull
private Integer projectId;
@Size(min = 3, max = 250, message = "Длина должна быть от 3 до 250")
private String name;
private boolean input = true;
public VariableForm() {
}
public VariableForm(Integer id, Integer projectId) {
this.id = id;
this.projectId = projectId;
}
public VariableForm(Integer id, Variable variable) {
this.id = id;
this.projectId = variable.getProject().getId();
this.name = variable.getName();
this.input = variable.isInput();
}
public Integer getProjectId() {
return projectId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setProjectId(Integer projectId) {
this.projectId = projectId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isInput() {
return input;
}
public void setInput(boolean input) {
this.input = input;
}
}

View File

@ -1,30 +0,0 @@
package ru.ulstu.fc.rule.model;
public class VariableValue {
private String fuzzyTerm;
private Double value;
public VariableValue() {
}
public VariableValue(String fuzzyTerm, Double value) {
this.fuzzyTerm = fuzzyTerm;
this.value = value;
}
public String getFuzzyTerm() {
return fuzzyTerm;
}
public void setFuzzyTerm(String fuzzyTerm) {
this.fuzzyTerm = fuzzyTerm;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
}

View File

@ -0,0 +1,12 @@
package ru.ulstu.fc.rule.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.ulstu.fc.project.model.Project;
import ru.ulstu.fc.rule.model.FuzzyRule;
import java.util.List;
public interface FuzzyRuleRepository extends JpaRepository<FuzzyRule, Integer> {
List<FuzzyRule> findByProject(Project project);
}

View File

@ -0,0 +1,8 @@
package ru.ulstu.fc.rule.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.ulstu.fc.rule.model.FuzzyTerm;
public interface FuzzyTermRepository extends JpaRepository<FuzzyTerm, Integer> {
}

View File

@ -1,7 +1,12 @@
package ru.ulstu.fc.rule.repository; package ru.ulstu.fc.rule.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import ru.ulstu.fc.project.model.Project;
import ru.ulstu.fc.rule.model.Variable; import ru.ulstu.fc.rule.model.Variable;
import java.util.List;
public interface VariableRepository extends JpaRepository<Variable, Integer> { public interface VariableRepository extends JpaRepository<Variable, Integer> {
List<Variable> findByProject(Project project);
} }

View File

@ -14,8 +14,8 @@ import com.fuzzylite.variable.OutputVariable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.ulstu.fc.rule.model.FuzzyTerm;
import ru.ulstu.fc.rule.model.OutputValue; import ru.ulstu.fc.rule.model.OutputValue;
import ru.ulstu.fc.rule.model.Term;
import ru.ulstu.fc.rule.model.Variable; import ru.ulstu.fc.rule.model.Variable;
import java.util.List; import java.util.List;
@ -48,13 +48,16 @@ public class FuzzyInferenceService {
final InputVariable input = new InputVariable(); final InputVariable input = new InputVariable();
input.setName(variable.getName()); input.setName(variable.getName());
input.setDescription(""); input.setDescription("");
input.setRange(0, variable.getTerms().get(variable.getTerms().size() - 1).getMax()); input.setRange(0, variable.getFuzzyTerms().get(variable.getFuzzyTerms().size() - 1).getCrispValue());
input.setEnabled(true); input.setEnabled(true);
input.setLockValueInRange(false); input.setLockValueInRange(false);
for (int i = 0; i < variable.getTerms().size(); i++) { double prev = 0;
Triangle term = new Triangle(variable.getTerms().get(i).getName(), for (int i = 0; i < variable.getFuzzyTerms().size(); i++) {
variable.getTerms().get(i).getMin(), Triangle term = new Triangle(variable.getFuzzyTerms().get(i).getDescription(),
variable.getTerms().get(i).getMax()); prev,
variable.getFuzzyTerms().get(i).getCrispValue(),
variable.getFuzzyTerms().get(i).getCrispValue() + variable.getFuzzyTerms().get(i).getCrispValue() - prev);
prev = term.getVertexB();
input.addTerm(term); input.addTerm(term);
} }
return input; return input;
@ -64,16 +67,20 @@ public class FuzzyInferenceService {
final OutputVariable output = new OutputVariable(); final OutputVariable output = new OutputVariable();
output.setName(variable.getName()); output.setName(variable.getName());
output.setDescription(""); output.setDescription("");
output.setRange(0, variable.getTerms().get(variable.getTerms().size() - 1).getMax()); output.setRange(0, variable.getFuzzyTerms().get(variable.getFuzzyTerms().size() - 1).getCrispValue());
output.setEnabled(true); output.setEnabled(true);
output.setAggregation(new Maximum()); output.setAggregation(new Maximum());
output.setDefuzzifier(new WeightedAverage()); output.setDefuzzifier(new WeightedAverage());
output.setDefaultValue(Double.NaN); output.setDefaultValue(Double.NaN);
output.setLockValueInRange(false); output.setLockValueInRange(false);
for (int i = 0; i < variable.getTerms().size(); i++) { double prev = 0;
Triangle term = new Triangle(variable.getTerms().get(i).getName(), for (int i = 0; i < variable.getFuzzyTerms().size(); i++) {
variable.getTerms().get(i).getMin(), Triangle term = new Triangle(
variable.getTerms().get(i).getMax()); variable.getFuzzyTerms().get(i).getDescription(),
prev,
variable.getFuzzyTerms().get(i).getCrispValue(),
variable.getFuzzyTerms().get(i).getCrispValue() + variable.getFuzzyTerms().get(i).getCrispValue() - prev);
prev = term.getVertexB();
output.addTerm(term); output.addTerm(term);
} }
return output; return output;
@ -94,6 +101,7 @@ public class FuzzyInferenceService {
mamdani.setImplication(new AlgebraicProduct()); mamdani.setImplication(new AlgebraicProduct());
mamdani.setActivation(new General()); mamdani.setActivation(new General());
rules.forEach(r -> mamdani.addRule(Rule.parse(r, engine))); rules.forEach(r -> mamdani.addRule(Rule.parse(r, engine)));
mamdani.addRule(new Rule());
return mamdani; return mamdani;
} }
@ -119,21 +127,20 @@ public class FuzzyInferenceService {
public List<OutputValue> getFuzzyInference(Map<String, Double> vals) { public List<OutputValue> getFuzzyInference(Map<String, Double> vals) {
return getFuzzyInference(getDemoRules(), vals, return getFuzzyInference(getDemoRules(), vals,
List.of(new Variable("возраст", List.of( List.of(new Variable("возраст", List.of(
new Term("молодой", 0.0, 40.0), new FuzzyTerm("молодой", 35.0),
new Term("средний", 20.0, 60.0), new FuzzyTerm("средний", 60.0),
new Term("старый", 50.0, 100.0)) new FuzzyTerm("старый", 100.0))
), ),
new Variable("доход", List.of( new Variable("доход", List.of(
new Term("небольшой", 0.0, 35000.0), new FuzzyTerm("небольшой", 35000.0),
new Term("средний", 20000.0, 100000.0), new FuzzyTerm("средний", 100000.0),
new Term("высокий", 80000.0, 500000.0)) new FuzzyTerm("высокий", 500000.0))
) )
), ),
new Variable("кредит", List.of( new Variable("кредит", List.of(
new Term("не_выдавать_кредит", 0.0, 1.0), new FuzzyTerm("небольшой", 20000.0),
new Term("небольшой", 1.0, 50000.0), new FuzzyTerm("средний", 100000.0),
new Term("средний", 25000.0, 100000.0), new FuzzyTerm("большой", 1000000.0)))
new Term("большой", 75000.0, 1000000.0)))
); );
} }

View File

@ -0,0 +1,41 @@
package ru.ulstu.fc.rule.service;
import org.springframework.stereotype.Service;
import ru.ulstu.fc.project.service.ProjectService;
import ru.ulstu.fc.rule.model.FuzzyRule;
import ru.ulstu.fc.rule.model.FuzzyRuleForm;
import ru.ulstu.fc.rule.repository.FuzzyRuleRepository;
@Service
public class FuzzyRuleService {
private final FuzzyRuleRepository ruleRepository;
private final ProjectService projectService;
public FuzzyRuleService(FuzzyRuleRepository ruleRepository, ProjectService projectService) {
this.ruleRepository = ruleRepository;
this.projectService = projectService;
}
public FuzzyRule getById(Integer id) {
return ruleRepository
.findById(id)
.orElseThrow(() -> new RuntimeException("Rule not found by id"));
}
public Object save(FuzzyRuleForm ruleForm) {
FuzzyRule rule;
if (ruleForm.getId() == null || ruleForm.getId() == 0) {
rule = new FuzzyRule();
} else {
rule = getById(ruleForm.getId());
}
rule.setProject(projectService.getById(ruleForm.getProjectId()));
rule.setContent(ruleForm.getContent());
return ruleRepository.save(rule);
}
public void delete(FuzzyRuleForm ruleForm) {
getById(ruleForm.getId());
ruleRepository.deleteById(ruleForm.getId());
}
}

View File

@ -0,0 +1,56 @@
package ru.ulstu.fc.rule.service;
import org.springframework.stereotype.Service;
import ru.ulstu.fc.rule.model.FuzzyTerm;
import ru.ulstu.fc.rule.model.FuzzyTermForm;
import ru.ulstu.fc.rule.repository.FuzzyTermRepository;
import java.util.Collections;
import java.util.List;
@Service
public class FuzzyTermService {
private final FuzzyTermRepository fuzzyTermRepository;
private final VariableService variableService;
public FuzzyTermService(FuzzyTermRepository fuzzyTermRepository,
VariableService variableService) {
this.fuzzyTermRepository = fuzzyTermRepository;
this.variableService = variableService;
}
public FuzzyTerm getById(Integer id) {
return fuzzyTermRepository
.findById(id)
.orElseThrow(() -> new RuntimeException("Term not found by id"));
}
public FuzzyTerm save(FuzzyTermForm fuzzyTermForm) {
FuzzyTerm term;
if (fuzzyTermForm.getId() == null || fuzzyTermForm.getId() == 0) {
term = new FuzzyTerm();
} else {
term = getById(fuzzyTermForm.getId());
}
term.setDescription(fuzzyTermForm.getDescription());
term.setCrispValue(fuzzyTermForm.getCrispValue());
FuzzyTerm ft = fuzzyTermRepository.save(term);
if (fuzzyTermForm.getId() == null || fuzzyTermForm.getId() == 0) {
variableService.addFuzzyTerm(fuzzyTermForm.getVariableId(), ft);
}
return ft;
}
public void delete(FuzzyTermForm fuzzyTermForm) {
getById(fuzzyTermForm.getId());
fuzzyTermRepository.deleteById(fuzzyTermForm.getId());
}
public List<FuzzyTerm> getByVariableId(Integer variableId) {
if (variableId == null || variableId == 0) {
return Collections.emptyList();
}
return variableService.getById(variableId).getFuzzyTerms();
}
}

View File

@ -1,38 +1,49 @@
package ru.ulstu.fc.rule.service; package ru.ulstu.fc.rule.service;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.ulstu.fc.rule.model.AddVariableForm; import ru.ulstu.fc.project.service.ProjectService;
import ru.ulstu.fc.rule.model.FuzzyTerm;
import ru.ulstu.fc.rule.model.Variable; import ru.ulstu.fc.rule.model.Variable;
import ru.ulstu.fc.rule.model.VariableForm;
import ru.ulstu.fc.rule.repository.VariableRepository; import ru.ulstu.fc.rule.repository.VariableRepository;
import java.util.List;
@Service @Service
public class VariableService { public class VariableService {
private final VariableRepository variableRepository; private final VariableRepository variableRepository;
private final ProjectService projectService;
public VariableService(VariableRepository variableRepository) { public VariableService(VariableRepository variableRepository, ProjectService projectService) {
this.variableRepository = variableRepository; this.variableRepository = variableRepository;
this.projectService = projectService;
} }
public Variable getById(Integer id) {
public List<Variable> getVars() { return variableRepository
return variableRepository.findAll(); .findById(id)
.orElseThrow(() -> new RuntimeException("Variable not found by id"));
} }
public void save(AddVariableForm addVariableForm) { public Variable save(VariableForm variableForm) {
if (addVariableForm.getId() == null) { Variable variable;
variableRepository.save(new Variable(addVariableForm.getName())); if (variableForm.getId() == null || variableForm.getId() == 0) {
variable = new Variable();
} else { } else {
Variable dbVar = variableRepository.findById(addVariableForm.getId()).orElseThrow(() -> new RuntimeException("Variable not found by id")); variable = getById(variableForm.getId());
dbVar.setName(addVariableForm.getName());
variableRepository.save(dbVar);
} }
variable.setProject(projectService.getById(variableForm.getProjectId()));
variable.setName(variableForm.getName());
variable.setInput(variableForm.isInput());
return variableRepository.save(variable);
} }
public AddVariableForm getAddVariableFormOrDefault(Integer id) { public void delete(VariableForm ruleForm) {
return id == null getById(ruleForm.getId());
? new AddVariableForm() variableRepository.deleteById(ruleForm.getId());
: new AddVariableForm(variableRepository.findById(id).orElseThrow(() -> new RuntimeException("Var not foubd by id"))); }
public void addFuzzyTerm(Integer variableId, FuzzyTerm ft) {
Variable var = getById(variableId);
var.getFuzzyTerms().add(ft);
variableRepository.save(var);
} }
} }

View File

@ -0,0 +1,40 @@
package ru.ulstu.fc.user.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.context.request.WebRequest;
import ru.ulstu.fc.user.model.User;
import ru.ulstu.fc.user.model.UserDto;
import ru.ulstu.fc.user.service.UserService;
@Controller
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/user/register")
public String showRegistrationForm(WebRequest request, Model model) {
UserDto userDto = new UserDto();
model.addAttribute("user", userDto);
return "register";
}
@PostMapping("/user/register")
public String registerUserAccount(
@ModelAttribute("user") @Valid UserDto userDto,
HttpServletRequest request,
Errors errors) {
userService.createUser(new User(userDto));
return "redirect:/login";
}
}

View File

@ -0,0 +1,77 @@
package ru.ulstu.fc.user.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import ru.ulstu.fc.config.Constants;
import ru.ulstu.fc.core.model.BaseEntity;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "is_users")
public class User extends BaseEntity {
@NotNull
@Pattern(regexp = Constants.LOGIN_REGEX)
@Size(min = 1, max = 50)
@Column(length = 50, unique = true, nullable = false)
private String login;
@NotNull
@Size(min = 60, max = 60)
@Column(name = "password_hash", length = 60, nullable = false)
private String password;
@ManyToMany
@JoinTable(
name = "is_user_role",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "user_role_name", referencedColumnName = "name")})
private Set<UserRole> roles;
public User() {
roles = new HashSet<>();
}
public User(String login, String password, Set<UserRole> roles) {
this.login = login;
this.password = password;
this.roles = roles;
}
public User(UserDto userDto) {
this.login = userDto.getLogin();
this.password = userDto.getPassword();
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login.toLowerCase();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Set<UserRole> getRoles() {
return roles;
}
public void setRoles(Set<UserRole> roles) {
this.roles = roles;
}
}

View File

@ -0,0 +1,40 @@
package ru.ulstu.fc.user.model;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
public class UserDto {
@NotNull
@NotEmpty
private String login;
@NotNull
@NotEmpty
private String password;
private String matchingPassword;
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;
}
public String getMatchingPassword() {
return matchingPassword;
}
public void setMatchingPassword(String matchingPassword) {
this.matchingPassword = matchingPassword;
}
}

View File

@ -0,0 +1,7 @@
package ru.ulstu.fc.user.model;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}

View File

@ -0,0 +1,50 @@
package ru.ulstu.fc.user.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@Entity
@Table(name = "is_user_roles")
public class UserRole {
@Id
@NotNull
@Size(max = 50)
@Column(length = 50, nullable = false)
private String name;
public UserRole() {
}
public UserRole(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
UserRole role = (UserRole) o;
return !(name != null ? !name.equals(role.name) : role.name != null);
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
return name != null ? name.hashCode() : 0;
}
}

View File

@ -0,0 +1,6 @@
package ru.ulstu.fc.user.model;
public class UserRoleConstants {
public static final String ADMIN = "ROLE_ADMIN";
public static final String USER = "ROLE_USER";
}

View File

@ -0,0 +1,105 @@
package ru.ulstu.fc.user.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import jakarta.validation.constraints.NotNull;
import ru.ulstu.fc.core.model.BaseEntity;
import java.util.Date;
@Entity
@Table(name = "is_user_sessions")
public class UserSession extends BaseEntity {
@NotNull
@Column(name = "session_id", nullable = false, unique = true)
private String sessionId;
@NotNull
@Column(name = "ip_address", nullable = false)
private String ipAddress;
@NotNull
@Column(nullable = false)
private String host;
@NotNull
@Column(name = "login_time", nullable = false)
@Temporal(TemporalType.TIMESTAMP)
private Date loginTime;
@Column(name = "logout_time")
@Temporal(TemporalType.TIMESTAMP)
private Date logoutTime;
@ManyToOne(optional = false)
@JoinColumn(name = "user_id")
private User user;
public UserSession() {
}
public UserSession(String sessionId, String ipAddress, String host, User user) {
this.sessionId = sessionId;
this.ipAddress = ipAddress;
this.host = host;
this.loginTime = new Date();
this.user = user;
}
public String getSessionId() {
return sessionId;
}
public String getIpAddress() {
return ipAddress;
}
public String getHost() {
return host;
}
public Date getLoginTime() {
return loginTime;
}
public Date getLogoutTime() {
return logoutTime;
}
public User getUser() {
return user;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public void setHost(String host) {
this.host = host;
}
public void setLoginTime(Date loginTime) {
this.loginTime = loginTime;
}
public void setLogoutTime(Date logoutTime) {
this.logoutTime = logoutTime;
}
public void setUser(User user) {
this.user = user;
}
public void close() {
this.logoutTime = new Date();
}
}

View File

@ -0,0 +1,15 @@
package ru.ulstu.fc.user.repository;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.ulstu.fc.user.model.User;
public interface UserRepository extends JpaRepository<User, Integer> {
User findOneByLoginIgnoreCase(String login);
@EntityGraph(attributePaths = "roles")
User findOneWithRolesById(int id);
@EntityGraph(attributePaths = "roles")
User findOneWithRolesByLogin(String login);
}

View File

@ -0,0 +1,7 @@
package ru.ulstu.fc.user.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.ulstu.fc.user.model.UserRole;
public interface UserRoleRepository extends JpaRepository<UserRole, String> {
}

View File

@ -0,0 +1,13 @@
package ru.ulstu.fc.user.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.ulstu.fc.user.model.UserSession;
import java.util.Date;
import java.util.List;
public interface UserSessionRepository extends JpaRepository<UserSession, Integer> {
UserSession findOneBySessionId(String sessionId);
List<UserSession> findAllByLogoutTimeIsNullAndLoginTimeBefore(Date date);
}

View File

@ -0,0 +1,21 @@
package ru.ulstu.fc.user.service;
import jakarta.servlet.http.HttpServletRequest;
public final class IpAddressResolver {
//private static final String CLIENT_IP_HEADER = "Client-IP";
//private static final String FORWARDED_FOR_HEADER = "X-Forwarded-For";
public static String getRemoteAddr(HttpServletRequest request) {
String headerClientIp = request.getHeader("");
String headerXForwardedFor = request.getHeader(HttpServletRequest.FORM_AUTH);
if (request.getRemoteAddr().isEmpty() && !headerClientIp.isEmpty()) {
return headerClientIp;
}
if (!headerXForwardedFor.isEmpty()) {
return headerXForwardedFor;
}
return request.getRemoteAddr();
}
}

View File

@ -0,0 +1,93 @@
package ru.ulstu.fc.user.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.ulstu.fc.user.model.User;
import ru.ulstu.fc.user.model.UserNotFoundException;
import ru.ulstu.fc.user.model.UserRole;
import ru.ulstu.fc.user.model.UserRoleConstants;
import ru.ulstu.fc.user.repository.UserRepository;
import ru.ulstu.fc.user.repository.UserRoleRepository;
import ru.ulstu.fc.user.utils.UserUtils;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@Service
@Transactional
public class UserService implements UserDetailsService {
private final Logger log = LoggerFactory.getLogger(UserService.class);
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
private final UserRoleRepository userRoleRepository;
@Value("${admin-password}")
private String adminPassword;
public UserService(PasswordEncoder passwordEncoder,
UserRepository userRepository,
UserRoleRepository userRoleRepository) {
this.passwordEncoder = passwordEncoder;
this.userRepository = userRepository;
this.userRoleRepository = userRoleRepository;
}
public User getUserByLogin(String login) {
return userRepository.findOneByLoginIgnoreCase(login);
}
@Override
public UserDetails loadUserByUsername(String username) {
final User user = userRepository.findOneByLoginIgnoreCase(username);
if (user == null) {
throw new UserNotFoundException(username);
}
return new org.springframework.security.core.userdetails.User(user.getLogin(),
user.getPassword(),
Optional.ofNullable(user.getRoles()).orElse(Collections.emptySet()).stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList()));
}
public User createUser(User user) {
if (getUserByLogin(user.getLogin()) != null) {
throw new RuntimeException(user.getLogin());
}
User dbUser = (user.getId() == null)
? user
: getUserById(user.getId());
//user.setRoles(Collections.singleton(new UserRole(UserRoleConstants.USER)));
dbUser.setPassword(passwordEncoder.encode(user.getPassword()));
dbUser.setLogin(user.getLogin());
dbUser = userRepository.save(dbUser);
log.debug("Created Information for User: {}", dbUser.getLogin());
return dbUser;
}
public User getUserById(Integer id) {
return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found by id"));
}
private void createDefaultUser(String login, String userRole) {
if (getUserByLogin(login) == null) {
UserRole role = userRoleRepository.save(new UserRole(userRole.toString()));
createUser(new User(login, login.equals("admin") ? adminPassword : login, Set.of(role)));
}
}
public void initDefaultAdmin() {
createDefaultUser("admin", UserRoleConstants.ADMIN);
}
public User getCurrentUser() {
return getUserByLogin(UserUtils.getCurrentUserLogin());
}
}

View File

@ -0,0 +1,43 @@
package ru.ulstu.fc.user.service;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import ru.ulstu.fc.config.Constants;
import java.io.IOException;
@Component
public class UserSessionLoginHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private final Logger log = LoggerFactory.getLogger(UserSessionLoginHandler.class);
private final UserSessionService userSessionService;
public UserSessionLoginHandler(UserSessionService userSessionService) {
super();
this.userSessionService = userSessionService;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
super.onAuthenticationSuccess(request, response, authentication);
final String login = authentication.getName();
final String ipAddress = IpAddressResolver.getRemoteAddr(request);
final String host = request.getRemoteHost();
log.debug("Authentication Success for {}@{} ({})", login, ipAddress, host);
HttpSession session = request.getSession(false);
if (session != null) {
final String sessionId = session.getId();
userSessionService.createUserSession(sessionId, login, ipAddress, host);
session.setAttribute(Constants.SESSION_ID_ATTR, sessionId);
session.setMaxInactiveInterval(Constants.SESSION_TIMEOUT_SECONDS);
}
}
}

View File

@ -0,0 +1,47 @@
package ru.ulstu.fc.user.service;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.stereotype.Component;
import ru.ulstu.fc.config.Constants;
import java.io.IOException;
@Component
public class UserSessionLogoutHandler extends SimpleUrlLogoutSuccessHandler {
private final Logger log = LoggerFactory.getLogger(UserSessionLogoutHandler.class);
private final UserSessionService userSessionService;
public UserSessionLogoutHandler(UserSessionService userSessionService) {
this.userSessionService = userSessionService;
setDefaultTargetUrl(Constants.LOGOUT_URL);
}
@Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if (authentication == null) {
super.onLogoutSuccess(request, response, authentication);
return;
}
final String login = authentication.getName();
final String ipAddress = IpAddressResolver.getRemoteAddr(request);
final String host = request.getRemoteHost();
log.debug("Logout Success for {}@{} ({})", login, ipAddress, host);
HttpSession session = request.getSession(false);
if (session != null) {
final String sessionId = session.getAttribute(Constants.SESSION_ID_ATTR).toString();
userSessionService.closeUserSession(sessionId);
session.removeAttribute(Constants.SESSION_ID_ATTR);
session.invalidate();
}
super.onLogoutSuccess(request, response, authentication);
}
}

View File

@ -0,0 +1,42 @@
package ru.ulstu.fc.user.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.ulstu.fc.user.model.User;
import ru.ulstu.fc.user.model.UserNotFoundException;
import ru.ulstu.fc.user.model.UserSession;
import ru.ulstu.fc.user.repository.UserSessionRepository;
@Service
@Transactional
public class UserSessionService {
private final Logger log = LoggerFactory.getLogger(UserSessionService.class);
private final UserSessionRepository userSessionRepository;
private final UserService userService;
public UserSessionService(UserSessionRepository userSessionRepository, UserService userService) {
this.userSessionRepository = userSessionRepository;
this.userService = userService;
}
public void createUserSession(String sessionId, String login, String ipAddress, String host) {
final User user = userService.getUserByLogin(login);
if (user == null) {
throw new UserNotFoundException(login);
}
userSessionRepository.save(new UserSession(sessionId, ipAddress, host, user));
log.debug("User session {} created for user {}@{} ({})", sessionId, login, ipAddress, host);
}
public void closeUserSession(String sessionId) {
final UserSession userSession = userSessionRepository.findOneBySessionId(sessionId);
if (userSession == null) {
throw new IllegalArgumentException(String.format("User session %s not found", sessionId));
}
userSession.close();
userSessionRepository.save(userSession);
log.debug("User session {} closed", sessionId);
}
}

View File

@ -0,0 +1,24 @@
package ru.ulstu.fc.user.utils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
public class UserUtils {
public static String getCurrentUserLogin() {
final SecurityContext securityContext = SecurityContextHolder.getContext();
if (securityContext == null) {
return null;
}
final Authentication authentication = securityContext.getAuthentication();
if (authentication.getPrincipal() instanceof UserDetails) {
final UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
return springSecurityUser.getUsername();
}
if (authentication.getPrincipal() instanceof String) {
return (String) authentication.getPrincipal();
}
return null;
}
}

View File

@ -1,11 +1,14 @@
spring.main.banner-mode=off spring.main.banner-mode=off
server.port=8080 server.port=8080
server.jetty.connection-idle-timeout=1000s server.jetty.connection-idle-timeout=1000s
admin-password=admin
# Available levels are: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF # Available levels are: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
logging.level.ru.ulstu=DEBUG logging.level.ru.ulstu=DEBUG
logging.level.sun.rmi.transport=off logging.level.sun.rmi.transport=off
logging.level.javax.management.remote.rmi=off logging.level.javax.management.remote.rmi=off
logging.level.java.rmi.server=off logging.level.java.rmi.server=off
logging.level.org.apache.tomcat=INFO
logging.level.org.apache.tomcat.util.net=WARN
extractor.custom-projects-dir= extractor.custom-projects-dir=
server.error.include-stacktrace=always server.error.include-stacktrace=always
server.error.include-exception=true server.error.include-exception=true
@ -18,6 +21,4 @@ spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.ddl-auto=update
# swagger-ui custom path
springdoc.swagger-ui.path=/swagger-ui.html

View File

@ -1,6 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru" <html lang="ru" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"> xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<title>Нечеткий контроллер</title> <title>Нечеткий контроллер</title>
@ -12,6 +13,7 @@
<link rel="stylesheet" href="/webjars/bootstrap-select/1.13.8/css/bootstrap-select.min.css"/> <link rel="stylesheet" href="/webjars/bootstrap-select/1.13.8/css/bootstrap-select.min.css"/>
<link rel="stylesheet" href="/webjars/font-awesome/4.7.0/css/font-awesome.min.css"/> <link rel="stylesheet" href="/webjars/font-awesome/4.7.0/css/font-awesome.min.css"/>
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-lg navbar-light bg-light" layout:fragment="navbar"> <nav class="navbar navbar-expand-lg navbar-light bg-light" layout:fragment="navbar">
<a class="navbar-brand" href="/">Нечеткий контроллер</a> <a class="navbar-brand" href="/">Нечеткий контроллер</a>
@ -21,18 +23,27 @@
</button> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item"> <li class="nav-item dropdown">
<a class="nav-link" href="/listVars">Переменные</a> <a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown"
</li> aria-haspopup="true" aria-expanded="false">Проекты</a>
<li class="nav-item"> <div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="nav-link" href="/listTerms">Термы</a> <a class="dropdown-item" href="/project/list">Список проектов</a>
</li> </div>
<li class="nav-item">
<a class="nav-link" href="/listRules">Правила</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/swagger-ui/index.html">API</a> <a class="nav-link" href="/swagger-ui/index.html">API</a>
</li> </li>
<li>
<div sec:authorize="isAuthenticated()">
<a class="nav-link" href="/logout">
<span sec:authentication="name">Bob</span>
(Выход)</a>
</div>
<div sec:authorize="!isAuthenticated()">
<a class="nav-link" href="/login">Вход</a>
</div>
</li>
</ul> </ul>
</div> </div>
</nav> </nav>
@ -56,4 +67,5 @@
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}">
<head>
</head>
<body>
<div class="container" layout:fragment="content">
<h5>Страница не найдена</h5>
<a href="/"><h6>Вернуться на главную</h6></a>
</div>
</body>
</html>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}">
<head>
</head>
<body>
<div class="container" layout:fragment="content">
<h5>Ошибка сервера</h5>
<a href="/"><h6>Вернуться на главную</h6></a>
</div>
</body>
</html>

View File

@ -0,0 +1,54 @@
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"
layout:decorate="~{default}">
<head>
<title>Редактирование терма</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<div class="container" layout:fragment="content">
<h3>Редактирование терма:</h3>
<form th:action="@{/fuzzyTerm/save}" th:object="${fuzzyTermForm}" method="post">
<input type="hidden" th:field="*{projectId}">
<input type="hidden" th:field="*{variableId}">
<input type="hidden" th:field="*{id}">
<div class="form-group">
<label for="name">Терм</label>
<input th:field="*{description}"
id="description"
type="text"
required
class="form-control"
placeholder="Терм">
<p th:if="${#fields.hasErrors('description')}"
th:class="${#fields.hasErrors('description')} ? error">
Не может быть пустым
</p>
</div>
<div class="form-group">
<label for="name">Значение</label>
<input th:field="*{crispValue}"
id="crispValue"
type="number"
required
class="form-control"
placeholder="Терм">
<p th:if="${#fields.hasErrors('crispValue')}"
th:class="${#fields.hasErrors('crispValue')} ? error">
Не может быть пустым
</p>
</div>
<button name="save" type="submit" class="btn btn-outline-dark">Сохранить</button>
<button name="delete"
type="submit"
class="btn btn-outline-dark"
th:if="*{id != 0}"
onclick="return confirm('Удалить запись?')">
Удалить
</button>
<a th:href="@{'/variable/edit/' + ${projectId}+'/' + ${variableId}}" class="btn btn-outline-dark">Отмена</a>
</form>
</div>
</html>

View File

@ -25,11 +25,11 @@
</div> </div>
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
<select id="select-age-antecedent" class="selectpicker m-2" data-live-search="true" <select id="select-age-antecedent" class="selectpicker m-2" data-live-search="true"
th:field="*{ageAntecedent}" th:field="*{ageValue}"
data-width="90%"> data-width="90%">
<option th:each="ageAntecedent : ${ageAntecedents}" <option th:each="ageValue : ${ageValues}"
th:value="${ageAntecedent.value}" th:value="${ageValue.crispValue}"
th:utext="${ageAntecedent.description}"> th:utext="${ageValue.description}">
</option> </option>
</select> </select>
</div> </div>
@ -40,11 +40,11 @@
</div> </div>
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
<select id="select-income-antecedent" class="selectpicker m-2" data-live-search="true" <select id="select-income-antecedent" class="selectpicker m-2" data-live-search="true"
th:field="*{incomeAntecedent}" th:field="*{incomeValue}"
data-width="90%"> data-width="90%">
<option th:each="incomeAntecedent : ${incomeAntecedents}" <option th:each="incomeValue : ${incomeValues}"
th:value="${incomeAntecedent.value}" th:value="${incomeValue.crispValue}"
th:utext="${incomeAntecedent.description}"> th:utext="${incomeValue.description}">
</option> </option>
</select> </select>
</div> </div>

View File

@ -1,42 +0,0 @@
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"
layout:decorate="~{default}">
<head>
<title>Список правил</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<div class="container" layout:fragment="content">
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th scope="col" colspan="10">Правила</th>
</tr>
</thead>
<tbody>
<tr th:each="dbRule: ${rules}">
<td><span class="badge badge-info">Если</span></td>
<td><span class="badge badge-success" th:text="${dbRule.firstAntecedent.description}"></span></td>
<td><span class="badge badge-success" th:text="${dbRule.firstAntecedentValue.antecedentValue}"></span></td>
<td><span class="badge badge-info">и</span></td>
<td><span class="badge badge-success" th:text="${dbRule.secondAntecedent.description }"></span></td>
<td><span class="badge badge-success" th:text="${dbRule.secondAntecedentValue.antecedentValue}"></span></td>
<td><span class="badge badge-info">то</span></td>
<td><span class="badge badge-warning" th:text="${dbRule.consequent}"></span></td>
<td>
<a role="button" class="btn btn-info" th:href="@{${@route.ADD_RULE} + '?ruleId=' + ${dbRule.id}}">
<i class="fa fa-pencil-square-o" aria-hidden="true"></i>
</a>
</td>
<td>
<a role="button" class="btn btn-danger" th:href="@{'deleteRule?id=' + ${dbRule.id}}"
onclick="return confirm('Удалить правило?')">
<i class="fa fa-times" aria-hidden="true"></i>
</a>
</td>
</tr>
</tbody>
</table>
<a href="/addRule" class="btn btn-outline-success">Добавить правило</a>
</div>
</html>

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}">
<head>
</head>
<body>
<nav layout:fragment="navbar">
<div class="navbar-header">
<a class="navbar-brand" href="/"><span class="ui-menuitem-text">Нечеткий контроллер</span></a>
</div>
</nav>
<div class="container" layout:fragment="content">
<div class="tab-content">
<div id="signin" class="tab-pane active">
<form th:action="@{/login}" method="post" class="margined-top-10">
<fieldset>
<div class="form-group">
<input type="text" name="username" id="username" class="form-control"
placeholder="Логин" required="true" autofocus="true"/>
</div>
<div class="form-group">
<input type="password" name="password" id="password" class="form-control"
placeholder="Пароль" required="true"/>
</div>
<button type="submit" class="btn btn-outline-dark">Войти</button>
<a href="/user/register" class="btn btn-outline-dark">Регистрация</a>
</fieldset>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}">
<head>
</head>
<body>
<nav layout:fragment="navbar">
<div class="navbar-header">
<a class="navbar-brand" href="/"><span class="ui-menuitem-text">Нечеткий контроллер</span></a>
</div>
</nav>
<div class="container" layout:fragment="content">
<div class="container-fluid">
<ul id="messages" class="feedback-panel">
<div class="alert alert-danger" role="alert">Ошибка входа</div>
</ul>
</div>
<div class="tab-content">
<div id="signin" class="tab-pane active">
<form th:action="@{/login}" method="post" class="margined-top-10">
<fieldset>
<div class="form-group">
<input type="text" name="username" id="username" class="form-control"
placeholder="Логин" required="true" autofocus="true"/>
</div>
<div class="form-group">
<input type="password" name="password" id="password" class="form-control"
placeholder="Пароль" required="true"/>
</div>
<button type="submit" class="btn btn-outline-dark btn-block">Войти</button>
</fieldset>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,86 @@
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"
layout:decorate="~{default}">
<head>
<title>Редактирование проекта</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<div class="container" layout:fragment="content">
<h3>Редактирование проекта:</h3>
<form th:action="@{/project/save}" th:object="${project}" method="post">
<input type="hidden" th:field="*{id}">
<div class="form-group">
<label for="name">Название</label>
<input th:field="*{name}" id="name" type="text" required class="form-control" placeholder="Название">
<p th:if="${#fields.hasErrors('name')}" th:class="${#fields.hasErrors('name')}? error">
Не может быть пустым
</p>
</div>
<div class="form-group">
<label th:text="'Дата создания: ' + ${#dates.format(project.createDate, 'dd.MM.yyyy HH:mm')}"></label>
</div>
<button name="save" type="submit" class="btn btn-outline-dark">Сохранить</button>
<button name="delete" type="submit" class="btn btn-outline-dark" onclick="return confirm('Удалить запись?')">
Удалить
</button>
<a href="/project/list" class="btn btn-outline-dark">Отмена</a>
</form>
<hr/>
<div class="row">
<div class="col col-md-6">
<h4> Список переменных</h4>
<div class="form-group">
<div class="row" th:each="v, iter : ${variables}">
<div class="col col-md-12">
<a th:href="@{'/variable/edit/' + ${projectId}+'/'+${v.id}}">
<span class="badge badge-light" th:text="${iter.index+1} + '. ' + ${v.name}"></span>
</a>
</div>
</div>
</div>
<a th:href="@{'/variable/edit/' + ${projectId}+'/0'}" class="btn btn-outline-dark">Добавить преременную</a>
</div>
<div class="col col-md-6">
<h4> Список правил</h4>
<div class="form-group">
<div class="row" th:each="r, iter : ${rules}">
<div class="col col-md-12">
<a th:href="@{'/rule/edit/' + ${projectId}+'/'+${r.id}}">
<span class="badge badge-light" th:text="${iter.index+1} + '. ' + ${r.content}"></span>
</a>
</div>
</div>
<!-- <div class="col col-md-2 offset-md-3">
<span class="badge badge-primary">Переменная</span>
</div>
<div class="col col-md-2">
<span class="badge badge-light">есть</span>
</div>
<div class="col col-md-2">
<span class="badge badge-success">значение</span>
</div>
<div class="col col-md-1">
<span class="badge badge-danger">И / ИЛИ</span>
</div>
<div class="col col-md-2 offset-md-3">
<span class="badge badge-primary">Переменная</span>
</div>
<div class="col col-md-2">
<span class="badge badge-light">есть</span>
</div>
<div class="col col-md-2">
<span class="badge badge-success">значение</span>
</div>
<div class="col col-md-1">
<span class="badge badge-danger">И / ИЛИ</span>
</div>-->
</div>
<a th:href="@{'/rule/edit/' + ${projectId}+'/0'}" th:if="${not #lists.isEmpty(variables)}"
class="btn btn-outline-dark">Добавить правило</a>
</div>
</div>
</html>

View File

@ -0,0 +1,22 @@
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"
layout:decorate="~{default}">
<head>
<title>Список проектов</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<div class="container" layout:fragment="content">
<a href="/project/edit/0" class="btn btn-outline-dark">
<i class="fa fa-plus-square" aria-hidden="true">Добавить проект</i>
</a>
<ul>
<li th:each="p : ${projects}">
<a th:href="@{'/project/edit/' + ${p.id}}">
<span th:text="${p.name} + ' от ' + ${#dates.format(p.createDate, 'dd.MM.yyyy HH:mm')}"></span>
</a>
</li>
</ul>
</div>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}">
<head>
<title>Список правил</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<div class="container" layout:fragment="content">
<h3> Список правил</h3>
<div class="rowд">
<div class="col col-md-12">
<span class="badge badge-light">1. Если</span>
</div>
<div class="col col-md-3">
<span class="badge badge-primary">Переменная</span>
</div>
<div class="col col-md-3">
<span class="badge badge-light">есть</span>
</div>
<div class="col col-md-3">
<span class="badge badge-success">значение</span>
</div>
<div class="col col-md-3">
<span class="badge badge-danger">И / ИЛИ</span>
</div>
</div>
</div>
</html>

View File

@ -0,0 +1,29 @@
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<div class="container" layout:fragment="content">
<h3>Регистрация</h3>
<form action="/user/register" th:object="${user}" method="POST" enctype="utf8">
<div class="form-group">
<label for="login">Логин</label>
<input th:field="*{login}" id="login" type="text" required class="form-control"
placeholder="Логин пользователя">
<p th:each="error: ${#fields.errors('login')}" th:text="${error}">Validation error</p>
</div>
<div class="form-group">
<label for="password">Пароль</label>
<input th:field="*{password}" id="password" type="text" required class="form-control"
placeholder="Пароль">
<p th:each="error : ${#fields.errors('password')}" th:text="${error}">Validation error</p>
</div>
<div class="form-group">
<label for="confirmPass">Подтверждение пароля</label>
<input id="confirmPass" type="password" th:field="*{matchingPassword}" required class="form-control"
placeholder="Подтверждение пароля"/>
</div>
<button type="submit" class="btn btn-outline-dark">Зарегистрировать</button>
<a href="/login" class="btn btn-outline-dark">Вернуться на страницу входа</a>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"
layout:decorate="~{default}">
<head>
<title>Редактирование правила</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<div class="container" layout:fragment="content">
<h3>Редактирование правила:</h3>
<form th:action="@{/rule/save}" th:object="${fuzzyRuleForm}" method="post">
<input type="hidden" th:field="*{projectId}">
<input type="hidden" th:field="*{id}">
<div class="form-group">
<label for="content">Правило</label>
<input th:field="*{content}"
id="content"
type="text"
required
class="form-control"
placeholder="Правило">
<p th:if="${#fields.hasErrors('content')}"
th:class="${#fields.hasErrors('content')}? error">
Не может быть пустым
</p>
</div>
<button name="save" type="submit" class="btn btn-outline-dark">Сохранить</button>
<button name="delete"
type="submit"
class="btn btn-outline-dark"
onclick="return confirm('Удалить запись?')">
Удалить
</button>
<a th:href="@{'/project/edit/' + ${projectId}}" class="btn btn-outline-dark">Отмена</a>
</form>
</div>
</html>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:th="http://www.w3.org/1999/xhtml"
layout:decorate="~{default}">
<head>
<title>Редактирование переменной</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<div class="container" layout:fragment="content">
<h3>Редактирование переменной:</h3>
<form th:action="@{/variable/save}" th:object="${variableForm}" method="post">
<input type="hidden" th:field="*{projectId}">
<input type="hidden" th:field="*{id}">
<div class="form-group">
<label for="name">Переменная</label>
<input th:field="*{name}" id="name" type="text" required class="form-control" placeholder="Переменная">
<p th:if="${#fields.hasErrors('name')}" th:class="${#fields.hasErrors('name')} ? error">
Не может быть пустым
</p>
</div>
<div class="form-group">
<input th:field="*{input}" id="input" type="checkbox">
<label for="input">Является входной переменной</label>
</div>
<button name="save" type="submit" class="btn btn-outline-dark">Сохранить</button>
<button name="delete" type="submit" class="btn btn-outline-dark" th:if="*{id != 0}"
onclick="return confirm('Удалить запись?')">
Удалить
</button>
<a th:href="@{'/project/edit/' + ${projectId}}" class="btn btn-outline-dark">Отмена</a>
<hr/>
<div class="row">
<div class="col col-md-6">
<h4> Список термов</h4>
<div class="form-group">
<div class="row" th:each="t, iter : ${fuzzyTerms}">
<div class="col col-md-12">
<a th:href="@{'/fuzzyTerm/edit/' + ${projectId} + '/' + ${variableId}+'/'+${t.id}}">
<span class="badge badge-light"
th:text="${iter.index+1} + '. ' + ${t.description}"></span>
</a>
</div>
</div>
</div>
<a th:href="@{'/fuzzyTerm/edit/' + ${projectId} + '/' + ${variableId}+'/0'}"
class="btn btn-outline-dark">Добавить терм</a>
</div>
</div>
</form>
</div>
</html>