[SpringBoot2.4.3] ログイン後のリダイレクト先指定

WebSecurityConfig.java
L .defaultSuccessUrl(“/test1/index”)でログイン後のURLを指定する

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.antMatchers("/","/home").permitAll()
				// 上記以外は認証が必要
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.loginPage("/login")
				.permitAll()
				.defaultSuccessUrl("/test1/index")
				.and()
			.logout()
				.permitAll();
	}
	@Bean
	@Override
	public UserDetailsService userDetailsService() {
		UserDetails user =
				User.withDefaultPasswordEncoder()
					.username("user")
					.password("pass")
					.roles("USER")
					.build();
		return new InMemoryUserDetailsManager(user);
	}	
}

なるほど。
ログイン認証をpostgresでやりたいな。

CakePHPのControllerやRouteの書き方

/src/Model/Table/PostsTable.php

namespace App\Model\Table;

use Cake\ORM\Table;

class PostsTable extends Table
{
	public function initialize(array $config) : void {
		$this->addBehavior('Timestamp');
	}
}

/templates/Posts/index.php
L 拡張子はctpではなくphpにする
https://book.cakephp.org/4/ja/views.html

<h1>Blog Posts</h1>

<ul>
	<?php foreach($posts as $post) : ?>
		<li><?= h($post->title); ?></li>
	<?php endforeach;?>
</ul>

/src/Controller/PostsController.php

// /posts/index
// /(controller)/(action)/(options)

namespace App\Controller;

class PostsController extends AppController{

	public function index(){
		$posts = $this->Posts->find('all');
		$this->set('posts', $posts);
	}
}

config/routes.php

    $builder->connect('/', ['controller' => 'Posts', 'action' => 'index']);

controller

$posts = $this->Posts->find('all')->order(['title'=>'DESC'])->limit(2)->where(['title like'=> '%3']);

3系と4系だと、大分書き方が変わってるな。特にctpからphpに変わってるのは驚きだな。

CakePHPの環境構築

$ php -i | grep intl
$ sudo yum -y install php-intl
$ sudo vi /etc/php.d/intl.ini

extension=intl.so

$ php -i | grep intl
PHP Warning: Module ‘intl’ already loaded in Unknown on line 0
/etc/php.d/20-intl.ini,
/etc/php.d/intl.ini
intl
intl.default_locale => no value => no value
intl.error_level => 0 => 0
intl.use_exceptions => 0 => 0

### mysql
create database cakephp;
use cakephp;
create table posts (
id int unsigned auto_increment primary key,
title varchar(255),
body text,
created datetime default null,
modified datetime default null
);

insert into posts (title, body, created) values
(‘title 1’, ‘body 1’, now()),
(‘title 2’, ‘body 2’, now()),
(‘title 3’, ‘body 3’, now());

select * from posts;

### cake install
$ composer create-project –prefer-dist cakephp/app myapp

config/app_local.php

            'username' => 'root',
            'password' => 'hoge',

            'database' => 'cakephp',

config/app.php

        'defaultLocale' => env('APP_DEFAULT_LOCALE', 'ja_JP'),
        'defaultTimezone' => env('APP_DEFAULT_TIMEZONE', 'Asia/Tokyo'),

$ bin/cake bake all posts
$ bin/cake server -H 192.168.33.10 -p 8000
http://192.168.33.10:8000/posts

あああああああああ、ちょっと思い出してきたー

[MySQLデータ移行]エクスポートとインポート

$ uname -a
Linux localhost 4.14.214-160.339.amzn2.x86_64 #1 SMP Sun Jan 10 05:53:05 UTC 2021 x86_64 x86_64 x86_64 GNU/Linu
$ mysql –version
mysql Ver 8.0.23 for Linux on x86_64 (MySQL Community Server – GPL)

$ mysql -u root -p
mysql> show databases;
mysql> use nonemail
mysql> show tables;
+————————+
| Tables_in_nonemail |
+————————+
| failed_jobs |
| migrations |
| password_resets |
| personal_access_tokens |
| sessions |
| users |
+————————+
mysql> select * from users;
-> usersテーブルに4件データが入っています。

### mysqlのデータエクスポート
$ mysqldump -u root -p –opt nonemail > database_name.sql
$ ls
database_name.sql

dumpファイルの中身
-> sql文が入っている

### mysqlのdumpファイルからデータインポート
$ ls
database_name.sql
$ mysql -u root -p newdatabase < database_name.sql Enter password: ERROR 1049 (42000): Unknown database 'newdatabase' -> ん? dbを作ってからでないとダメ??

mysql> create database newdatabase;
Query OK, 1 row affected (0.01 sec)
$ mysql -u root -p newdatabase < database_name.sql Enter password: mysql> use newdatabase;
mysql> show tables;
+————————+
| Tables_in_newdatabase |
+————————+
| failed_jobs |
| migrations |
| password_resets |
| personal_access_tokens |
| sessions |
| users |
+————————+
6 rows in set (0.00 sec)
mysql> select * from users;

ちゃんとデータも入ってます^^

なるほど、mysqlのデータ移行の方法は理解した。

[SpringBoot2.4.3] uploadを任意の場所にファイルを保存

Controller.java
L new FileOutputStream(${path})で、保存場所を指定する

	@RequestMapping(value="upload", method=RequestMethod.POST)
    public void handle(
            HttpServletResponse response,
            @RequestParam MultipartFile file
            ) {
        if(file.isEmpty()) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }
        try {
            BufferedInputStream in = new BufferedInputStream(file.getInputStream());
            BufferedOutputStream out = new BufferedOutputStream(
                    new FileOutputStream("./target/" + file.getOriginalFilename()));
        } catch (IOException e) {
            throw new RuntimeException("Error uploading file.", e);
        }
    }

なるほど、resourcesのstaticの中に置くこともできますね。

new FileOutputStream(“./src/main/resources/static/file/” + file.getOriginalFilename())

### filenameをreturn
res.getWriter().write(file.getOriginalFilename());

OK、後は基本機能としてはログインのみなんだよなー

[SpringBoot2.4.3] ファイルアップロードのcontroller

import javax.servlet.http.HttpServletResponse;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

@Controller
@RequestMapping("/contract")
public class ContractController {

	@GetMapping("input")
	public String contract() {
		return "contract/input";
	}
	
	@RequestMapping(value="upload", method=RequestMethod.POST)
    public void handle(
            HttpServletResponse response,
            @RequestParam MultipartFile file
            ) {
        if(file.isEmpty()) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }
        try {
            BufferedInputStream in = new BufferedInputStream(file.getInputStream());
            BufferedOutputStream out = new BufferedOutputStream(
                    new FileOutputStream(file.getOriginalFilename()));
        } catch (IOException e) {
            throw new RuntimeException("Error uploading file.", e);
        }
    }
}

html

<form action="/contract/upload" method="post" enctype="multipart/form-data">
    <div class="form-group">
    <div class="custom-file">
      <input type="file" name="file" class="custom-file-input" id="inputFile">
    </div>
  </div>
  <br>
<button type="submit" class="btn btn-primary" id="upload" value="upload">登録</button>	
</form>

BufferedInputStreamとBufferedOutputStreamの使い方がイマイチよくわからんな。。

[SpringBoot2.4.3] ファイルアップロード機能を作る

まず、webpackで簡単にフロントを作成します

続いて、そのまま、src/main/resources/templates/contract/input.html に流し込みます。

<form action="/upload" enctype="multipart/form-data">
    <div class="form-group">
    <div class="custom-file">
      <input type="file" name="file" class="custom-file-input" id="inputFile">
      <!-- <label class="custom-file-label" for="inputFile"></label> -->
    </div>
  </div>
  <br>
<button type="button" class="btn btn-primary">登録</button>	
</form>

続いてControllerを作ります。適当にContractController.javaとします。

package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/contract")
public class ContractController {

	@GetMapping("input")
	public String contract() {
		return "contract/input";
	}
}

ここからfile保存を実装したい。。。

[SpringBoot2.4.3] Service

package com.example.demo;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import org.springframework.stereotype.Service;

@Service
public class MyDataService {
	
	@PersistenceContext
	private EntityManager entityManager;
	
	@SuppressWarnings("unchecked")
	public List<MyData> getAll(){
		return (List<MyData>) entityManager
				.createQuery("from MyData").getResultList();
	}
	
	public MyData get(int num) {
		return (MyData)entityManager
				.createQuery("from MyData where id = " + num)
				.getSingleResult();
	}
	
	public List<MyData> find(String fstr){
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		CriteriaQuery<MyData> query = builder.createQuery(MyData.class);
		Root<MyData> root = query.from(MyData.class);
		query.select(root).where(builder.equal(root.get("name"), fstr));
		List<MyData> list = null;
		list = (List<MyData>) entityManager.createQuery(query).getResultList();
		return list;
	}
}

[SpringBoot2.4.3] エンティティの連携

複数のテーブルが関連して動かしたい場合は、「アソシエーション」という機能によりエンティティ同士を連携して処理する

@OneToOne,@OneToMany, @ManyToOne, @ManyToMany

package com.example.demo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

import org.hibernate.validator.constraints.NotEmpty;

@Entity
@Table(name = "msgdata")
public class MsgData {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column
	@NotNull
	private long id;
	
	@Column
	private String title;
	
	@Column(nullable = false)
	@NotEmpty
	private String message;
	
	@ManyToOne
	private MyData mydata;
	
	public MsgData() {
		super();
		mydata = new MyData();
	}
	
	public long getId() {
		return id;
	}
	
	public void setId(long id) {
		this.id = id;
	}
	
	public String getTitle() {
		return title;
	}
	
	public void setTitle(String title) {
		this.title = title;
	}
	
	public String getMessage() {
		return message;
	}
	
	public void setMessage(String message) {
		this.message = message;
	}
	
	public MyData getMyData() {
		return mydata;
	}
	
	public void setMydata(MyData mydata) {
		this.mydata = mydata;
	}
}

MsgDataRepository.java

package com.example.demo.repositories;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.example.demo.MsgData;

@Repository
public interface MsgDataRepository extends JpaRepository<MsgData, Long>{

}

MsgDataDao.java

package com.example.demo;

import java.io.Serializable;
import java.util.List;

public interface MsgDataDao<T> {
	
	public List<MsgData> getAll();
	public MsgData findById(long id);
}

MsgDataDaoImpl.java

package com.example.demo;

import java.util.List;
import javax.persistence.EntityManager;

import org.springframework.stereotype.Repository;

@SuppressWarnings("rawtypes")
@Repository
public class MsgDataDaoImpl implements MsgDataDao<MsgDataDao>{
	
	private EntityManager entityManager;
	
	public MsgDataDaoImpl() {
		super();
	}
	public MsgDataDaoImpl(EntityManager manager) {
		entityManager = manager;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<MsgData> getAll(){
		return entityManager
				.createQuery("from MsgData")
				.getResultList();
	}
	
	@Override
	public MsgData findById(long id) {
		return (MsgData)entityManager
				.createQuery("from MsgData where id = "
						+ id).getSingleResult();
	}

}

showMsgData.html

	<h1 th:text="${title}">MyMsg page</h1>
	<p th:text="${msg}"></p>
	<table>
		<form method="post" action="/msg" th:object="${formModel}">
		<input type="hidden" name="id" th:value="*{id}">
		<tr>
			<td><label for="title">タイトル</label></td>
			<td><input type="text" name="title" th:value="*{title}"></td>
		</tr>
		<tr>
			<td><label for="message">メッセージ</label></td>
			<td><textarea name="message" th:text="*{message}"></textarea></td>
		</tr>
		<tr>
			<td><label for="mydata">MYDATA_ID</label></td>
			<td><input type="text" name="mydata"></td>
		</tr>
		<tr>
			<td></td>
			<td><input type="submit"></td>
		</tr>
		</form>
	</table>
	<hr>
	<table>
		<tr><th>ID</th><th>名前</th><th>タイトル</th></tr>
		<tr th:each="obj : ${datalist}">
			<td th:text="${obj.id}"></td>
			<td th:text="${obj.mydata.name}"></td>
			<td th:text="${obj.title}"></td>
		</tr>
	</table>

MsgDataController.java

package com.example.demo;

import java.util.List;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.repositories.MsgDataRepository;

@Controller
public class MsgDataController {

		@Autowired
		MsgDataRepository repository;
		
		@PersistenceContext
		EntityManager entityManager;
		
		MsgDataDaoImpl dao;
		
		@RequestMapping(value = "/msg", method=RequestMethod.GET)
		public ModelAndView msg(ModelAndView mav) {
			mav.setViewName("showMsgData");
			mav.addObject("title", "Sample");
			mav.addObject("msg","MsgDataのsampleです");
			MsgData msgdata = new MsgData();
			mav.addObject("formModel", msgdata);
			List<MsgData> list = (List<MsgData>)dao.getAll();
			mav.addObject("datalist", list);
			return mav;
		}
		
		@RequestMapping(value="/msg", method=RequestMethod.POST)
		public ModelAndView msgform(
				@Valid @ModelAttribute MsgData msgdata,
				Errors result,
				ModelAndView mav) {
			if(result.hasErrors()) {
				mav.setViewName("showMsgData");
				mav.addObject("title", "Sample [Error]");
				mav.addObject("msg", "値を再チェックして下さい");
				return mav;
			} else {
				repository.saveAndFlush(msgdata);
				return new ModelAndView("redirect:/msg");
			}
		}
		
		@PostConstruct
		public void init() {
			System.out.println("ok");
			dao = new MsgDataDaoImpl(entityManager);
		}
		
}

んん?あれ?

[SpringBoot2.4.3] Repository

Repository

@Repository
public interface MyDataRepository extends JpaRepository<MyData, Long> {
	
	@Query("SELECT d FROM MyData d ORDER BY d.name")
	List<MyData> findAllOrderByName();
// 省略

Controller

	@RequestMapping(value="/", method=RequestMethod.GET)
	public ModelAndView index(
			ModelAndView mav) {
		mav.setViewName("index");
		mav.addObject("msg","this is sample content.");
		Iterable<MyData> list = repository.findAllOrderByName();
		mav.addObject("datalist", list);
		return mav;
	}

よく出来てるなー
エンティティ自体にクエリを持たせるか、リポジトリに用意するか、どちらでもできる。