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:
  • 5.4.2 Configuration:
  • 5.4.3 Like:
  • 5.4.4 View Count:


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);
}











한국정보시스템개발원 |
Hankook Information System Institute

austiny@snu.ac.kr / austiny@gatech.edu