[JAVA] I made Mugen Kura Sushi Gacha

GoToEat campaign started in October Do you use it? Ministry of Agriculture, Forestry and Fisheries GoToEat Campaign

I used to use it often, but it seems that "Mugen Kura Sushi" is popular in the streets. You can eat at the points you get with GoToEat, and you can get points again with that meal, so you can eat Kura Sushi many times at a good price. (Aside from the moral story) Kura Sushi Official GoToEat Campaign Page

In order to receive points at GoToEat, it is necessary to eat at least 500 yen including tax for lunch and 1000 yen or more including tax for dinner at Kura Sushi. Therefore, I made "Infinite Kura Sushi Gacha" that randomly displays the Kura Sushi menu at a price that exceeds the applicable price.

Click here for the web application I made ↓ ** Mugen Kura Sushi Gacha **

This time I used the "Spring Boot" framework. The whole code is published on github ↓ https://github.com/yutwoking/eternalKurazushi

I'll leave this article as a step in creating these apps in the future.

Premises such as application creation environment and framework used

Rough flow

  1. Implementation of logic part --The reading part of the menu from the official Kura Sushi website --Stored / extracted part in database --Gacha logic part

  2. Spring Boot framework part --build.gradle description --Creating a launcher --Creating a controller --html creation (view creation)

  3. Web application publication (AWS)

1. Implementation of logic part

Menu reading from Kura Sushi official website

First, load the menu from the official website. It is necessary to analyze the html of Official Menu Site. It is analyzed using a library called Jsoup.

Jsoup usage reference https://www.codeflow.site/ja/article/java-with-jsoup

The implementation of the analysis part is as follows

LoadMenu.java


private static List<MenuModel>  loadMenuFromSite(String url) throws IOException{
    	List<MenuModel> models = new LinkedList<>();

    	Document doc = Jsoup.connect(url).get();
    	Elements menusBySection = doc.select(".section-body");
    	for (Element section : menusBySection) {
    		String sectionName  = section.select(".menu-section-header h3").text();
    		if (!StringUtils.isEmpty(sectionName)) {
    			Elements menus  = section.select(".menu-list").get(0).select(".menu-item");
    			for (Element menu : menus) {
    				String name = menu.select(".menu-name").text();
    				Elements summary = menu.select(".menu-summary li");

    				if (summary.size() >2) {
    					int price  = stringToInt(summary.get(0).select("p").get(0).text());
    					int kcal  = stringToInt(summary.get(0).select("p").get(1).text());
    					String area = summary.get(1).select("p").get(1).text();
    					boolean takeout = toBoolean(summary.get(2).select("p").get(1).text());
    					models.add(new MenuModel(name, price, kcal, area, takeout, sectionName));
    				} else if (summary.size() == 2) {
    					int price  = stringToInt(summary.get(0).select("p").get(0).text());
    					int kcal  = stringToInt(summary.get(0).select("p").get(1).text());
    					String area = "";
    					boolean takeout = toBoolean(summary.get(1).select("p").get(1).text());
    					models.add(new MenuModel(name, price, kcal, area, takeout, sectionName));
    				}
    			}
    		}
    	}
    	return models;
    }

As a basic usage of JSoup,


Document doc = Jsoup.connect(url).get();

Read the url html with

Elements elements = doc.select(".section-body");

Use the select method like this to extract the relevant element.

I regret that the implementation code is hard to see because the if and for statements are nested. .. ..

Storage / extraction part in database

Next is the implementation around the database. I'm using a java DB access framework called Doma2. Click here for Doma2 official

Doma has the following features.

· Use annotation processing to generate and validate code at compile time · Can map column values on the database to behavioral Java objects -A SQL template called 2-way SQL can be used. -Java 8 java.time.LocalDate, java.util.Optional and java.util.stream.Stream can be used. -No dependence on libraries other than JRE

It is a framework that I often use because I personally like the fact that it can be managed with sql files. The usage is official and Japanese, so it is good to refer to it. The implementation code is omitted here. The whole code is published on github ↓ https://github.com/yutwoking/eternalKurazushi

Gacha logic part

Gacha.java



public static List<MenuModelForSearch> getResult(Areas area, boolean isLunch){
		List<MenuModelForSearch> result  = new ArrayList<>();
//Store 500 for lunch and 1000 for dinner in threshold
		int threshold = getThreshold(isLunch);
//Store all menus in candidates
		List<MenuModelForSearch> candidates = MenuCaches.getSingleton().getMenuList(area, isLunch);

//Check if the total amount of the acquired menu exceeds the threshold, and randomly add menus from candidates until it exceeds.
		while (isOverThreshold(threshold, result) == false) {
			addElement(result, candidates);
		}
//Finally check if the lunch menu is included. If the lunch menu is included, the result will be the lunch menu only.
		checkIncludeLunchMenu(result);

		return result;
	}

See Whole Code for each method.

2. Spring Boot framework part

Regarding SpringBoot, I referred to the following site. https://qiita.com/gosutesu/items/961b71a95daf3a2bce96 https://qiita.com/opengl-8080/items/eb3bf3b5301bae398cc2 https://note.com/ymzk_jp/n/n272dc9e5c5d3

Description of build.gradle

Add plugins and libraries to build.gradle so that gradle can use the Spring Boot framework.

The part where the actual build.gradle of this time is commented as // spring-boot added part in ↓ is added.

build.gradle


plugins {
    // Apply the java plugin to add support for Java
    id 'java'

    // Apply the application plugin to add support for building a CLI application
    id 'application'
    id 'eclipse'
    id 'com.diffplug.eclipse.apt' version '3.25.0'

    id 'org.springframework.boot' version '2.3.5.RELEASE' //spring-boot postscript part
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'//spring-boot postscript part
}

version = '2.26.0-SNAPSHOT'
ext.dependentVersion = '2.24.0'

task copyDomaResources(type: Sync)  {
    from sourceSets.main.resources.srcDirs
    into compileJava.destinationDir
    include 'doma.compile.config'
    include 'META-INF/**/*.sql'
    include 'META-INF/**/*.script'
}

compileJava {
    dependsOn copyDomaResources
    options.encoding = 'UTF-8'
}

compileTestJava {
    options.encoding = 'UTF-8'
    options.compilerArgs = ['-proc:none']
}

repositories {
    mavenCentral()
    mavenLocal()
    maven {url 'https://oss.sonatype.org/content/repositories/snapshots/'}
}

dependencies {
    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'

    // https://mvnrepository.com/artifact/org.jsoup/jsoup
    compile group: 'org.jsoup', name: 'jsoup', version: '1.13.1'

    // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
    compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.11'

    annotationProcessor "org.seasar.doma:doma:${dependentVersion}"
    implementation "org.seasar.doma:doma:${dependentVersion}"
    runtimeOnly 'com.h2database:h2:1.3.175'
    // https://mvnrepository.com/artifact/org.postgresql/postgresql
    compile group: 'org.postgresql', name: 'postgresql', version: '42.2.8'

	// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
    compile  group: 'com.zaxxer', name: 'HikariCP', version: '3.4.1'

	// https://mvnrepository.com/artifact/javax.inject/javax.inject
    compile group: 'javax.inject', name: 'javax.inject', version: '1'

    // https://mvnrepository.com/artifact/io.vavr/vavr
	compile group: 'io.vavr', name: 'vavr', version: '0.10.2'

	// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf
	compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: '2.3.5.RELEASE'//spring-boot postscript part

    // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web
	compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.3.5.RELEASE'//spring-boot postscript part

	// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter
	compile group: 'org.springframework.boot', name: 'spring-boot-starter', version: '2.3.5.RELEASE'//spring-boot postscript part

}


//spring-Added bootJar task to make boot project a service
bootJar { 
    launchScript()
}

application {
    // Define the main class for the application
    mainClassName = 'eternalKurazushi.ServerLuncher'
}

eclipse {
    classpath {
        file {
            whenMerged { classpath ->
                classpath.entries.removeAll { it.path == '.apt_generated' }
            }
            withXml { provider ->
                def node = provider.asNode()
                // specify output path for .apt_generated
                node.appendNode( 'classpathentry', [ kind: 'src', output: 'bin/main', path: '.apt_generated'])
            }
        }
    }
    jdt {
        javaRuntimeName = 'JavaSE-11'
    }
}

Creating a launcher

ServerLuncher.java



@SpringBootApplication
public class ServerLuncher {

	public static void main(String[] args) throws Exception {
        SpringApplication.run(ServerLuncher.class, args);
        LoadMenu.init();
        MenuCaches.getSingleton().load();
    }

}

All you have to do is add the @SpringBootApplication annotation and implement SpringApplication.run.

LoadMenu.init();
MenuCaches.getSingleton().load();

This part reads the menu when the server starts and stores it in the DB, so that the menu also has a memory. Actually, with this configuration, I may not need a DB, but I am also using a DB in case it is expanded in the future (probably not expanded).

Creating a controller

FrontController.java



@Controller
public class FrontController {
	@RequestMapping("/")
	public String index() {
		return "index";
	}

	@RequestMapping(value = "/result", method = RequestMethod.POST)
	public String getResult(@RequestParam("radio_1") String eatTime, @RequestParam("radio_2") String areaString, Model model) {
		if (StringUtils.isEmpty(eatTime) || StringUtils.isEmpty(eatTime)) {
			return "error";
		}

		boolean isLunch = eatTime.equals("lunch") ? true : false;
		Areas area = Areas.Eastern Japan;
		if (areaString.equals("West Japan")) {
			area = Areas.West Japan;
		} else if (areaString.equals("Kyushu-Okinawa")) {
			area = Areas.Kyushu;
		}

		List<MenuModelForSearch> gachaResult = Gacha.getResult(area, isLunch);
		model.addAttribute("list", gachaResult);
		model.addAttribute("sum", getSumString(gachaResult));
		model.addAttribute("time", eatTime);
		model.addAttribute("area", areaString);
		return "result";
	}

Implement the controller using the @Controller annotation. Specify the corresponding path with @RequestMapping annotation. This is similar to jax-rs. Receive a value from html by using the @RequestParam annotation.

model.addAttribute("list", gachaResult);

You can pass a value to html with addAttribute. In this example, the value of gachaResult is passed to html with the variable name list.

return "result";

Returns the template html of / resources / templates / [Return value of Controller] .html. In this example, /resources/templates/result.html is read and returned.

html creation (view creation)

result.html


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Infinite Kura Sushi Gacha</title>
</head>
<body bgcolor=#99FFFF>
	<div style="text-align: center">
		<main>
			<h1>Infinite Kura Sushi Gacha</h1>
			<p th:text="${time} + '  /  ' + ${area}" class="description"></p>
			<table border="1" align="center">
				<tr>
					<th>type</th>
					<th>Product name</th>
					<th>Price (excluding tax)</th>
					<th>calorie</th>
					<!--
					<th>Service area</th>
					<th>Takeaway</th>
					-->
				</tr>
				<tr th:each="menu : ${list}">
					<td th:text="${menu.type}"></td>
					<td th:text="${menu.name}"></td>
					<td th:text="${menu.price} + 'Circle'"></td>
					<td th:text="${menu.kcal} + 'kcal'"></td>
					<!--
					<td th:text="${menu.area}"></td>
					<td th:text="${menu.takeout} ? 'Yes' : '不Yes'"></td>
					-->
				</tr>
			</table>
			<h3>
				<p th:text="${sum}" class="sum"></p>
			</h3>
			<br>

			<form method="POST" action="/result">
				<input type="hidden" name="radio_1" th:value="${time}"> <input
					type="hidden" name="radio_2" th:value="${area}">
				<div style="margin: 2rem 0rem">
					<input type="submit" value="Turn the gacha again under the same conditions"
						style="width: 250px; height: 50px">
				</div>
			</form>
			<form method="GET" action="/">
				<div style="margin: 2rem 0rem">
					<input type="submit" value="Return">
				</div>
			</form>
		</main>
	</div>
</body>
</html>

th: value = "$ {variable name}" Then, the value received from the controller can be used. You can pass a value to the controller by creating an input and specifying a name using the form tag.

3. Web application publication (AWS)

This time, I used AWS to publish the web application. It's a simple app, so I used it

it's dark. Recommended for beginners as this book is very polite and can be practiced. [Network & Server Construction from Amazon Web Services Basics Revised Edition](https://www.amazon.co.jp/Amazon-Web-Services-%E5%9F%BA%E7%A4%8E%E3%81% 8B% E3% 82% 89% E3% 81% AE% E3% 83% 8D% E3% 83% 83% E3% 83% 88% E3% 83% AF% E3% 83% BC% E3% 82% AF- % E3% 82% B5% E3% 83% BC% E3% 83% 90% E3% 83% BC% E6% A7% 8B% E7% AF% 89 / dp / 4822237443)

Finally

It's been a while since I created something outside of work. Programming at work is fun, but programming in private is also different fun (selecting a framework, creating an environment, etc.), so I would like to do it regularly to improve my skills.

If I have time, I would like to work on the following.

--Obtain and set your own domain --Code refactoring --https conversion protocol for web applications ――In this article, I wrote the procedure quite quickly, so I will divide it into several articles and summarize them one by one.

Recommended Posts

I made Mugen Kura Sushi Gacha
I made StringUtils.isBlank
I made roulette in Java.
I made a chat app.