Skip to content

Commit 2bae984

Browse files
authored
Merge pull request #34 from heesane/server
Auto stash before revert of "devops Traefik & NginxProxyManager"
2 parents 6fc5a3f + 0d842a0 commit 2bae984

File tree

11 files changed

+428
-0
lines changed

11 files changed

+428
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package hhs.server.api.util;
2+
3+
import com.amazonaws.services.s3.AmazonS3;
4+
import com.amazonaws.services.s3.model.CannedAccessControlList;
5+
import com.amazonaws.services.s3.model.GetObjectRequest;
6+
import com.amazonaws.services.s3.model.PutObjectRequest;
7+
import com.amazonaws.services.s3.model.S3Object;
8+
import com.amazonaws.services.s3.model.S3ObjectInputStream;
9+
import hhs.server.domain.model.type.PictureType;
10+
import java.io.File;
11+
import java.io.FileOutputStream;
12+
import java.io.IOException;
13+
import java.net.URLEncoder;
14+
import java.nio.charset.StandardCharsets;
15+
import java.security.MessageDigest;
16+
import java.security.NoSuchAlgorithmException;
17+
import java.util.Base64;
18+
import java.util.Objects;
19+
import java.util.Optional;
20+
import lombok.RequiredArgsConstructor;
21+
import lombok.extern.slf4j.Slf4j;
22+
import org.springframework.beans.factory.annotation.Value;
23+
import org.springframework.http.HttpHeaders;
24+
import org.springframework.http.HttpStatus;
25+
import org.springframework.http.MediaType;
26+
import org.springframework.http.ResponseEntity;
27+
import org.springframework.stereotype.Component;
28+
import org.springframework.web.multipart.MultipartFile;
29+
30+
@Component
31+
@RequiredArgsConstructor
32+
@Slf4j
33+
public class PictureManager {
34+
35+
@Value("${cloud.aws.s3.bucket}")
36+
private String bucket;
37+
38+
private final AmazonS3 amazonS3;
39+
40+
public String upload(
41+
Long userId,
42+
String projectName,
43+
MultipartFile multipartFile,
44+
PictureType pictureType
45+
) throws IOException {
46+
47+
// system_architecture/1_1.jpg
48+
// erd/1_1.jpg
49+
String newFileName =
50+
pictureType.getFolderPrefix() +
51+
"/" + userId + "_" + projectName + "." +
52+
Objects.requireNonNull(multipartFile.getOriginalFilename()).split("\\.")[1];
53+
54+
File uploadFile = convert(multipartFile).orElseThrow(
55+
() -> new IllegalArgumentException("MultipartFile -> File 전환 실패")
56+
);
57+
58+
// 업로드한 사진의 URL 반환
59+
return upload(newFileName, uploadFile);
60+
61+
}
62+
63+
private String upload(
64+
String newFileName,
65+
File uploadFile
66+
) {
67+
68+
String uploadImageUrl = putS3(uploadFile, newFileName);
69+
70+
// convert()함수로 인해서 로컬에 생성된 File 삭제 (MultipartFile -> File 전환 하며 로컬에 파일 생성됨)
71+
removeNewFile(uploadFile);
72+
73+
return uploadImageUrl; // 업로드된 파일의 S3 URL 주소 반환
74+
75+
}
76+
77+
private String putS3(
78+
File uploadFile,
79+
String fileName
80+
) {
81+
82+
amazonS3.putObject(
83+
new PutObjectRequest(bucket, fileName, uploadFile)
84+
.withCannedAcl(CannedAccessControlList.PublicRead) // PublicRead 권한으로 업로드 됨
85+
);
86+
return amazonS3.getUrl(bucket, fileName).toString();
87+
88+
}
89+
90+
private void removeNewFile(
91+
File targetFile
92+
) {
93+
94+
if (targetFile.delete()) {
95+
log.debug("파일이 삭제되었습니다.");
96+
} else {
97+
log.error("{} 파일이 삭제되지 못했습니다.", targetFile.getName());
98+
}
99+
}
100+
101+
private Optional<File> convert(
102+
MultipartFile file
103+
) throws IOException {
104+
105+
String fileOriginalName = file.getOriginalFilename();
106+
File convertFile = new File(Objects.requireNonNull(fileOriginalName)); // 업로드한 파일의 이름
107+
108+
if (convertFile.createNewFile()) {
109+
try (FileOutputStream fos = new FileOutputStream(convertFile)) {
110+
fos.write(file.getBytes());
111+
}
112+
return Optional.of(convertFile);
113+
}
114+
115+
// 새로 만들어진 파일이 아닌 경우,
116+
return Optional.empty();
117+
}
118+
119+
public ResponseEntity<byte[]> download(
120+
String fileName,
121+
PictureType pictureType
122+
) throws IOException {
123+
124+
S3Object awsS3Object = amazonS3.getObject(
125+
new GetObjectRequest(bucket, pictureType.getFolderPrefix() + "/" + fileName));
126+
127+
S3ObjectInputStream s3is = awsS3Object.getObjectContent();
128+
129+
byte[] bytes = s3is.readAllBytes();
130+
131+
String downloadedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
132+
.replace("+", "%20");
133+
134+
HttpHeaders httpHeaders = new HttpHeaders();
135+
httpHeaders.setContentType(MediaType.IMAGE_JPEG);
136+
httpHeaders.setContentLength(bytes.length);
137+
httpHeaders.setContentDispositionFormData("attachment", downloadedFileName);
138+
139+
return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
140+
}
141+
142+
public boolean diff(
143+
String originalHashValue,
144+
MultipartFile newFile
145+
) {
146+
try {
147+
String newHashValue = calculateSHA256Base64(newFile);
148+
return !originalHashValue.equals(newHashValue);
149+
} catch (NoSuchAlgorithmException | IOException e) {
150+
log.info("Fail to calculate hash value");
151+
}
152+
return false;
153+
}
154+
155+
public String calculateSHA256Base64(
156+
MultipartFile file
157+
) throws NoSuchAlgorithmException, IOException {
158+
MessageDigest digest = MessageDigest.getInstance("SHA-256");
159+
byte[] hashBytes = digest.digest(file.getBytes());
160+
161+
// Base64 인코딩으로 해시 값을 문자열로 변환
162+
return Base64.getEncoder().encodeToString(hashBytes);
163+
}
164+
165+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package hhs.server.api.util;
2+
3+
4+
import hhs.server.domain.aop.lock.DistributedLock;
5+
import hhs.server.domain.persistence.Comments;
6+
import hhs.server.domain.persistence.Projects;
7+
import hhs.server.domain.persistence.elasticsearch.ProjectDocuments;
8+
9+
public class UpdateManager {
10+
11+
@DistributedLock
12+
public static void incrementProjectCommentCount(ProjectDocuments projectDocuments, Projects projects) {
13+
projectDocuments.update(projects);
14+
}
15+
16+
@DistributedLock
17+
public static void updateProjectLikeCount(Projects project, Long likeCount) {
18+
project.updateLikeCounts(likeCount);
19+
}
20+
21+
@DistributedLock
22+
public static void updateCommentLikeCount(Comments comments, Long likeCount) {
23+
comments.updateLikes(likeCount);
24+
}
25+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package hhs.server.common.config;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
5+
@EnableJpaRepositories(basePackages = "hhs.server.domain.repository.jpa")
6+
@Configuration
7+
public class JpaConfig {
8+
9+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package hhs.server.common.validator;
2+
3+
import hhs.server.common.exceptions.ExceptionCode;
4+
import hhs.server.common.exceptions.exception.NotFoundException;
5+
import hhs.server.domain.persistence.Comments;
6+
import hhs.server.domain.persistence.Projects;
7+
import hhs.server.domain.persistence.User;
8+
import hhs.server.domain.repository.jpa.CommentsRepository;
9+
import hhs.server.domain.repository.jpa.ProjectRepository;
10+
import hhs.server.domain.repository.jpa.UserRepository;
11+
import lombok.RequiredArgsConstructor;
12+
import org.springframework.stereotype.Component;
13+
14+
@Component
15+
@RequiredArgsConstructor
16+
public class Validator {
17+
18+
private final CommentsRepository commentsRepository;
19+
20+
private final ProjectRepository projectRepository;
21+
22+
private final UserRepository userRepository;
23+
24+
public Comments validateAndGetComment(Long commentId) {
25+
return commentsRepository.findById(commentId).orElseThrow(
26+
() -> new NotFoundException(ExceptionCode.COMMENTS_NOT_FOUND)
27+
);
28+
}
29+
30+
public void isCommentExist(Long commentId) {
31+
if (!commentsRepository.existsById(commentId)) {
32+
throw new NotFoundException(ExceptionCode.COMMENTS_NOT_FOUND);
33+
}
34+
}
35+
36+
public Projects validateAndGetProject(Long projectId) {
37+
// 프로젝트 정보 검증
38+
return projectRepository.findByIdWithDetail(projectId).orElseThrow(
39+
() -> new NotFoundException(ExceptionCode.PROJECT_NOT_FOUND));
40+
}
41+
42+
public void isProjectExist(Long projectId) {
43+
if (!projectRepository.existsById(projectId)) {
44+
throw new NotFoundException(ExceptionCode.PROJECT_NOT_FOUND);
45+
}
46+
}
47+
48+
public User validateAndGetUser(Long userId) {
49+
return userRepository.findById(userId)
50+
.orElseThrow(
51+
() -> new NotFoundException(ExceptionCode.USER_NOT_FOUND)
52+
);
53+
}
54+
55+
public void isUserExist(Long userId) {
56+
if (!userRepository.existsById(userId)) {
57+
throw new NotFoundException(ExceptionCode.USER_NOT_FOUND);
58+
}
59+
}
60+
}

devops/command.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# command:
2+
# - "--api.insecure=false"
3+
# - "--providers.docker=true"
4+
# - "--providers.docker.exposedbydefault=false"
5+
# - "--providers.docker.network=home_network"
6+
# - "--entrypoints.web.address=:80"
7+
# - "--entrypoints.websecure.address=:443"
8+
# - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
9+
# - "--entryPoints.web.http.redirections.entrypoint.scheme=https"
10+
# - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
11+
# - "--certificatesresolvers.letsencrypt.acme.email=tees3359@gmail.com"
12+
# - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
13+
# - "--log.level=DEBUG"
14+
# - "--accesslog=true"
15+
# - "--accesslog.filepath=/traefik/logs/access.log"
16+
# - "--accesslog.bufferingsize=100"
17+
# - "--accesslog.format=json"
18+
# - "--accesslog.fields.defaultmode=keep"
19+
# - "--accesslog.fields.headers.defaultmode=keep"
20+
# - "--accesslog.fields.headers.names.X-Forwarded-For=keep"
21+
# - "--api.dashboard=true"
22+
# - "--metrics.prometheus=true"
23+
# - "--metrics.prometheus.buckets=0.100000, 0.300000, 1.200000, 5.000000"
24+
# - "--metrics.prometheus.addEntryPointsLabels=true"
25+
# - "--metrics.prometheus.addServicesLabels=true"
26+
# - "--entryPoints.metrics.address=:8091"
27+
# - "--metrics.prometheus.entryPoint=metrics"

devops/traefik.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
global:
2+
checkNewVersion: true
3+
sendAnonymousUsage: true
4+
5+
api:
6+
dashboard: true
7+
insecure: false
8+
9+
entryPoints:
10+
web:
11+
address: ":80"
12+
http:
13+
redirections:
14+
entryPoint:
15+
to: websecure
16+
scheme: https
17+
websecure:
18+
address: ":443"
19+
metrics:
20+
address: ":8091"
21+
22+
providers:
23+
docker:
24+
endpoint: "unix:///var/run/docker.sock"
25+
exposedByDefault: false
26+
network: home_network
27+
28+
certificatesResolvers:
29+
letsencrypt:
30+
acme:
31+
32+
storage: "/letsencrypt/acme.json"
33+
httpChallenge:
34+
entryPoint: web
35+
36+
log:
37+
level: DEBUG
38+
39+
accessLog:
40+
filePath: /traefik/logs/access.log
41+
bufferingSize: 100
42+
format: json
43+
fields:
44+
defaultMode: keep
45+
headers:
46+
defaultMode: keep
47+
names:
48+
X-Forwarded-For: keep
49+
50+
metrics:
51+
prometheus:
52+
buckets: [ 0.100000, 0.300000, 1.200000, 5.000000 ]
53+
entryPoint: metrics
54+
addEntryPointsLabels: true
55+
addServicesLabels: true

k3s/traefik-config.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
apiVersion: helm.cattle.io/v1
2+
kind: HelmChartConfig
3+
metadata:
4+
name: traefik
5+
namespace: kube-system
6+
spec:
7+
valuesContent: |-
8+
ports:
9+
web:
10+
port: 80
11+
exposedPort: 80
12+
websecure:
13+
port: 443
14+
exposedPort: 443
15+
custom-8000:
16+
port: 8000
17+
exposedPort: 8000
18+
protocol: TCP
19+
additionalArguments:
20+
- "--entryPoints.custom-8000.address=:8000"

k3s/traefik-dashboard.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: traefik.containo.us/v1alpha1
2+
kind: IngressRoute
3+
metadata:
4+
name: traefik-dashboard
5+
namespace: kube-system
6+
spec:
7+
entryPoints:
8+
- web
9+
routes:
10+
- match: Host(`192.168.55.139`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
11+
kind: Rule
12+
services:
13+
- name: api@internal
14+
kind: TraefikService
15+
port: 9000
16+
- name: dashboard2@internal
17+
port: 9000

0 commit comments

Comments
 (0)