5. Advanced Project
5. 4 . Like & View Count
The typical "like" feature on a website is a simple interface element that allows users to express positive feedback on content. The main purposes and functionalities are as follows:
1. Encouraging User Engagement:
- Users can easily express their opinions with a simple click.
2. Providing Social Proof:
- A high number of likes indicates the popularity of the content and gives other users a sense of trust.
3. Personalized Feedback:
- The website analyzes like data to understand users' preferences and provides personalized content recommendations.
4. Improving Algorithms:
- The number of likes influences the frequency and ranking of content exposure.
5. Supporting Content Creators:
- Creators receive feedback through the number of likes, indicating how well their content is received.
Functionality:
- Button Interface: A clickable button that provides visual feedback when clicked.
- Data Storage: Click information is stored in a database for analysis.
Here's a detailed list of the topics
5.4.1 Basic Project:
application.properties
spring.application.name=securityexam
# Database Setting
spring.datasource.dbcp2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/securityexam
spring.datasource.username=root
spring.datasource.password=1111
# JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.show_sql=true
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
create Database
create Member.java Class
package com.example.securityexam;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
@Data
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer mid;
@Column(unique = true)
private String username; // for SpringSecurity Policy
private String password; // for SpringSecurity Policy
private boolean enabled; // for SpringSecurity Policy
private String role; // for SpringSecurity Policy
@Column(unique = true)
private String memail;
private LocalDateTime mdate;
}
After writing Member.java, when you run the server, the table is automatically generated.
create SecurityConfig.java Class
package com.example.securityexam;
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.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
;
return http.build();
}
}
3.1.3 Basic Sign up , in , out:
MemberRepository.java
package com.example.securityexam;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MemberRepository extends JpaRepository<Member, Integer> {
}
MemberService.java / MemberServiceImpl.java
package com.example.securityexam;
public interface MemberService {
void create(Member member);
}
package com.example.securityexam;
import java.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class MemberServiceImpl implements MemberService {
@Autowired
private MemberRepository memberRepository;
@Override
public void create(Member member) {
member.setEnabled(true);
member.setRole("ROLE_USER"); //ROLE_ADMIN, ROLE_MANAGER, ROLE_PAID...
member.setMdate(LocalDateTime.now());
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
member.setPassword(passwordEncoder.encode(member.getPassword()));
memberRepository.save(member);
}
}
MemberController.java
package com.example.securityexam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class MemberController {
@Autowired
private MemberService memberService;
@GetMapping("/signup")
public String signup() {
return "signup";
}
@PostMapping("/signup")
public String signup(Member member) {
memberService.create(member);
return "signup";
}
}
signup.html
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<form action="/signup" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<p>ID : <input type="text" name="username">
<p>PW : <input type="password" name="password">
<p>Mail : <input type="text" name="memail">
<p><input type="submit" value="Sign up">
</form>
</body>
</html>
!! important !! you must include CSRF TOKEN
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
Login check with Spring Security
MemberRepository
add --> Optional<Member> findByusername(String username); // login check
package com.example.securityexam;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MemberRepository extends JpaRepository<Member, Integer> {
Optional<Member> findByusername(String username); // login check
}
MemberSecurityService.java ( for login check )
package com.example.securityexam;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class MemberSecurityService implements UserDetailsService {
@Autowired
private MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Member> tmember = memberRepository.findByusername(username);
if (tmember.isEmpty()) {
throw new UsernameNotFoundException("You need to Sign up first...");
}
Member member = tmember.get();
List<GrantedAuthority> authorities = new ArrayList<>();
if ("ROLE_USER".equals(member.getRole())) {
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
} else if ("ROLE_MANAGER".equals(member.getRole())) {
authorities.add(new SimpleGrantedAuthority("ROLE_MANAGER"));
} else {
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
return new User(member.getUsername(), member.getPassword(), authorities);
}
}
add beans to SecurityConfig.java
package com.example.securityexam;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
.formLogin((formLogin) -> formLogin
.loginPage("/login")
.defaultSuccessUrl("/")
)
.logout((logout) -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true))
;
return http.build();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
signup.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<span sec:authorize="isAnonymous()">Login First...<a href="/login">log in</a></span>
<span sec:authorize="isAuthenticated()">Welcome...<span th:text="${#authentication.name}"></span>
<span th:text="${#authentication.authorities}"></span>
<a href="/logout">log out</a>
</span>
<form action="/signup" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<p>ID : <input type="text" name="username">
<p>PW : <input type="password" name="password">
<p>Mail : <input type="text" name="memail">
<p><input type="submit" value="Sign up">
</form>
</body>
</html>
signin.html
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<form action="/login" method="post">
<div th:if="${param.error}">
<div class="alert alert-danger">
Please, Check your ID or Password...
</div>
</div>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<p>ID : <input type="text" name="username">
<p>PW : <input type="password" name="password">
<p><input type="submit" value="Sign in">
</form>
</body>
</html>
5.4.3 Like:
Entity
package com.example.adminproject.board;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import com.example.adminproject.customer.Customer;
import com.example.adminproject.reply.Reply;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import lombok.Data;
@Data
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer bid;
@Column(length = 200)
private String btitle;
@Column(columnDefinition = "TEXT")
private String bcontent;
@Column
private String bimg;
@ManyToOne
private Customer bauthor;
@OneToMany(mappedBy = "rboard", cascade = CascadeType.REMOVE)
private List<Reply> replyList;
private LocalDateTime bdate;
@ManyToMany
Set<Customer> blike;
}
Service
@Autowired
private CustomerService customerService;
@Override
public void blike(Integer bid) {
Optional<Board> ob = boardRepository.findById(bid);
ob.get().getBlike().add(customerService.authen());
boardRepository.save(ob.get());
}
Controller
@GetMapping("/blike/{bid}")
public String blike(@PathVariable("bid") Integer bid) {
boardService.blike(bid);
return "redirect:/board/readdetail/" + bid;
}
readdetail.html
<br>
<a href="javascript:void(0);" class="recommend" th:data-uri="@{|/board/blike/${board.bid}|}">
좋아요
<span th:text="$#lists.size(board.blike)}"></span>
</a>
<script>
const recommend_elements = document.getElementsByClassName("recommend");
Array.from(recommend_elements).forEach(function(element) {
element.addEventListener('click', function(){
if(confirm("정말 추천하시겠습니까?")) {
location.href = this.dataset.uri;
};
});
});
</script>
ViewCount
Board.java
private int viewCount;
Repository
package com.example.adminproject.board;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
public interface BoardRepository extends JpaRepository<Board, Integer> {
@Modifying
@Transactional
@Query(value = "UPDATE board SET view_count = view_count + 1 WHERE bid = :bid", nativeQuery= true )
void increseViewCount(@Param("bid") Integer bid);
}
Service
@Transactional
@Override
public void increseViewCount(Integer bid) {
boardRepository.increseViewCount(bid);
}