イメージ検索項目(VIG)移行/分解-2

19910 ワード

ソースコード
https://github.com/GeonDev/VIG
プロジェクトの進行中、私たちは現在の実施に注目しているだけなので、簡単な設定で問題を解決したり、大量のコードを繰り返して問題を解決したり、後で問題を解決したりすることがよくあります.これは言い訳ですが、4人のすべてのコードを検査し、修正するのに十分な実力と時間がありません.
まず、最初の変更に設定を中断して、不要なログインチェックを減らします.

Handler Interceptor設定


ログインしていないユーザーが特定のURLにアクセスすることを防止し、ユーザーが誤ったURLを使用してアクセスすることを防止します.実際、これは簡単な構成可能な部分ですが、どのURLパスを使用するかを完全に決定していません.テストしにくいように設定していません.これにより、コントローラのワークフローの制御が可能になります.

これらの不要なif文を削除するために割り込みが設定されています.
@Component
public class CertificationInterceptor implements HandlerInterceptor{
	
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        HttpSession session = request.getSession();
        User loginVO = (User) session.getAttribute("user");
 
        if(ObjectUtils.isEmpty(loginVO)){
            response.sendRedirect("/checkLogin");
         
            return false;
        }else{
            session.setMaxInactiveInterval(30*60);
            return true;
        }
        
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        // TODO Auto-generated method stub
        
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // TODO Auto-generated method stub
        
    }

}
割り込みで実行される機能を決定するクラスを作成しました.後で進歩するかもしれませんが、特定のURLにアクセスする前にログインしたかどうかを検証するための簡単なコードを作成しました.WebCinfigクラスを作成して適用すると、割り込み操作は終了します.
@Configuration
public class WebCinfig implements WebMvcConfigurer {
	
	/* 로그인 인증 Interceptor 설정 */   
	@Autowired   
	private CertificationInterceptor certificationInterceptor;   
   
	@Value("${checkUrlList}")
	String checkUrls;
	
	@Value("${enableUrlList}")
	String enableUrls;
	
	
	@Override   
	public void addInterceptors(InterceptorRegistry registry) {
		
		//addPathPatterns 해당 패턴에 해당하는 URL을 인터럽트한다.
		//excludePathPatterns 해당 패턴에 해당하는 URL은 인터럽트하지 않는다.
		List<String> addUrlList = new ArrayList<>(Arrays.asList(checkUrls.split(",")));
		
		List<String> excludeUrlList = new ArrayList<>(Arrays.asList(enableUrls.split(",")));
				
		registry.addInterceptor(certificationInterceptor).addPathPatterns(addUrlList).excludePathPatterns(excludeUrlList);
		
		
	}  

}
会社で見かける項目の中には、中断するURLと無視するURLのハードコーディングがあります.
割り込み後に複数のブランチがあるため,URLを直接比較してハードコーディングを行う可能性があるが,これは良い方法とは考えられず,これはよく見られる.propertiesという名前の別のファイルから値をロードする方法で適用されます.

AOPの適用


概念的には、AOPが共同で実行するロジックを分離し、ビジネスロジックに専念できることを知っていますが、これらのロジックをどのように利用するかは分かりません.また、スプリング上で設定すると複雑なので、これらのロジックは適用されていません.スプリングガイドでは比較的簡単に実施できるのでAOPを適用してみた.
まずPOMxmlにaop Dependencyを追加します.
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>		  
</dependency>
アプリケーションクラス(親)に@EnableAsspectJAutoProxyを作成し、spring BootにAOPの使用を通知します.
package com.vig;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableAspectJAutoProxy
@EnableScheduling
@MapperScan(basePackageClasses = VigApplication.class)
@SpringBootApplication
@PropertySource("classpath:common.properties")
public class VigApplication {
	
	
	public static void main(String[] args) {
		SpringApplication.run(VigApplication.class, args);
	}

}
AOPで実行する機能を定義する方法を作成します.aopというパッケージを追加し、Logger Asspectというクラスを作成しました.
プライマリ・コードは、コントローラ・メソッドの実行時間を理解する機能を提供します.
この機能をAOPに移行して不要なコードを減らします.
@Around、@before、@afterなどの機能は選択可能ですが、個人的には最も利用率の高い@Aroundが汎用ソースを作成したと思います.
package com.vig.aop;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class LoggerAspect {

	public static final Logger logger = LogManager.getLogger(LoggerAspect.class); 


	@Around("execution(* com.vig.controller.*Controller.get*(..))")
	public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {
		
		
		//가지고온 joinPoint의 타입이름을 갖고온다 -> 여기서는 컨트롤러 이름
		String name = joinPoint.getSignature().getDeclaringTypeName();
		
		//전달되는 메소드의 파라미터를 담는다.
		Object[] parms = joinPoint.getArgs();		
		
		long startTime = System.currentTimeMillis();
		
		//Around로 가져온 프로세스가 실행된다.
		Object ret = joinPoint.proceed();
		
		// 메소드가 실행될때 까지 걸리는 실행시간 측정
		long time = System.currentTimeMillis() - startTime;
	
		
		logger.info(name + "." + joinPoint.getSignature().getName() + "() " + "WorkTime : "+ (time/1000.0f));
	
		if(parms != null ) {
			for(Object t : parms) {
				logger.info("ParameterType :" + t.getClass().getName());
			}
		}

		
		return ret;
	}
}
スプリングと比較してAOP機能を簡素化した.AOPはより多くの機能を実現できるように思われるが,特定のユーザのアクセス履歴を格納するなどの機能と一致しない汎用論理に用いられる.
最初にAOPを適用してみた目的は,シードを開くときにHistoryを保存する機能をスキップすることであった.JoinPointは、FeedControllerによって以前に処理された履歴を格納する値を提供します.History保存機能は@Beforeとして扱われる.
	@Before("execution(* com.vig.controller.FeedController.getFeed(..))")
	public void  addHistory(JoinPoint joinPoint) throws Exception  {
		
		int feedId = (int)joinPoint.getArgs()[0];
		HttpSession session = (HttpSession)joinPoint.getArgs()[1];
		HttpServletRequest request = (HttpServletRequest)joinPoint.getArgs()[2];
				
		//ip로 조회수 counting 하는 부분
		String ipAddress = CommonUtil.getUserIp(request);

		Feed feed = feedService.getFeed(feedId);
		User user = (User)session.getAttribute("user");

		
		// 로그인한 유저정보가 있는지 체크 - 히스토리를 남기는 부분입니다.
		History history = new History();		
		history.setWatchUser(user);
		history.setHistoryType(0);
		history.setShowFeed(feed);
		history.setIpAddress(ipAddress);
		
			
			//같은 기록의 히스토리가 있는지 체크
			if(historyService.getViewHistory(history) == 0 ) {				
				
				historyService.addHistory(history);
				feedService.updateViewCount(feedId);
				
				logger.info("FeedId : "+ feedId+" FeedViewCount History update" );
				logger.info("UserCode : "+ user.getUserCode()+" FeedView History update" );
			}
			
			//유저가 로그인한 경우
			if(user != null) {
				//히스토리 기록에 열람기록은 추가
				historyService.addHistory(history);
			}			
		}
AOPを適用するとパラメータ値や順序が変化してAOPコードも変化するので完璧な答えではないと思います.
AOP利用を再考すべき

コントローラビジネスロジック分離


MVCモードの学習では,少なくともビューファイルやDAOは分離の原則に従っていると考えられるビジネス層と表現層を分離して実施することを学習した.個人が論理を分離する際、最も厄介な問題は、サービスとコントローラをどのように区別するかです.
従来の実施方法は、サービスをDAOに接続し、サービス方法をDAO方法と1:1に一致させることである.これは複雑な論理ではなく、サービスが複数の異なる部分で使用できると考えられているコントローラであり、追加の演算が必要な場合、コントローラによって演算されます.
@Service
public class FeedService {
	
	@Autowired	  
	private FeedMapper feedMapper;

	public FeedService() {	}


	public void addFeed(Feed feed) throws Exception {		
		feedMapper.addFeed(feed);
	}
}
FeedServiceでaddFeedを実行するには、feedMapperを簡単に使用します.呼び出しaddFeed()が終了します.
@RequestMapping(value = "addFeed", method = RequestMethod.POST)
public ModelAndView addFeed(@RequestParam("keyword") String keyword, @ModelAttribute("feed") Feed feed,
                                   @ModelAttribute("category") Category category,@RequestParam("uploadFile") List<MultipartFile> files, 
                                   @SessionAttribute("user") User user,@ModelAttribute("joinUser") JoinUser joinUser) throws Exception {
		
	feed.setWriter(user);									
	feed.setFeedCategory(category);        
	feedServices.addFeed(feed);
							
                            
        String path = context.getRealPath("/");              
        if(OS.contains("win")) {
        	//워크스페이스 경로를 받아온다.
            path = path.substring(0,path.indexOf("\\.metadata"));         
            path +=  uploadPath;           
        }else {
        	//실제 톰켓 데이터가 저장되는 경로를 가리킨다.
        	path =  realPath;
        }
         
        // 이후에 VISION API을 이용하여 이미지에서 키워드를 추출)중략.......
       
	
	}
FeedControllerがaddFeed()を実行する場合はFeedServiceです.addFeed()を呼び出し、記憶画像の位置を決定し、VISION APIから画像を抽出する機能.
コントローラ上でこの操作を実行するのは問題ありませんが、可読性が悪く、レイヤがビジネスロジックを実行することを示すのは良い選択だと思いますが、コードを見て実際の操作を開始すると、どのロジックを許可すべきかを考え始めました.
したがって、この場合、データを作成または設定する操作は、これらの論理をサービスに移動することを前提として、いくつかの論理をサービスに移動します.

RestSearchControlの変更


既存のRestSearch Controlからカテゴリまたは個人固有の推奨イメージを出力するには、
コントローラは大量の演算を実行する必要があるため、コードの可読性は以下のように劣っています.
FeedServiceは、これらの機能をすぐに処理し、いくつかの重複機能を個別の方法でバンドルおよび非表示に処理するシードリストをロードしないようにすることで、コードをクリーンアップします.
//선택된 카테고리에 해당하는 피드를 리턴한다.
@RequestMapping(value = "json/getSearchCategoryResult")
public Map<String, Object> getSearchCategoryResult(@RequestBody Map<String, String> jsonData, HttpSession session, @CookieValue(value = "searchKeys", defaultValue = "", required = false) String searchKeys) throws Exception {
				
		Map<String, Object> map = new HashMap<String, Object>();
		
		Search search = new Search();		
		
		search.setCurrentPage(Integer.valueOf(jsonData.get("currentPage")));
		search.setPageSize(pageSize);
		
		//카테고리 ID 세팅
		search.setSearchType(Integer.parseInt(jsonData.get("category")));
		
		//로그인한 유저 정보를 받아옴
		User user = (User)session.getAttribute("user");
		
		List<Feed> feedlist = new ArrayList<Feed>();
		
		
		//선택된 카테고리가 사용자 추천인지 체크
		if(search.getSearchType() ==  10012) {
			
			//로그인 하지 않았다면 조회수가 가장 많은 피드를 추천
			if(user == null) {
				feedlist = feedServices.getHightViewFeedList(search);
				
			}else {
			// 로그인 한 유저에게 피드를 추천한다.------------------------------------------------------//					
				
				Search tempSearch = new Search();
				tempSearch.setKeyword(user.getUserCode());
				tempSearch.setPageSize(20);
				
				//일반 피드를 본 기록을 가지고 온다.
				tempSearch.setSearchType(0);
				//첫페이지 양만 가지고 옴
				tempSearch.setCurrentPage(1);				
				
				//최근 본 피드정보 20개를 가지고 온다.
				List<History> historyList =	historyServices.getHistoryList(tempSearch);					
				
				if(historyList.size() > 0) {					
					List<ImageKeyword> keywordList = new ArrayList<ImageKeyword>();
					
					//최근 본 피드의 썸네일 키워드 리스트를 가지고 온다.
					for(History history : historyList) {						
						keywordList.addAll(history.getShowFeed().getKeywords());
					}
					
					keywordList = addkeywordListFromCookis(keywordList,searchKeys);					
					
					tempSearch.setKeywords(CommonUtil.checkEqualKeyword(keywordList));
					tempSearch.setPageSize(pageSize);
					tempSearch.setCurrentPage(Integer.valueOf(jsonData.get("currentPage")));
					
					feedlist = CommonUtil.checkEqualFeed(feedServices.getRecommendFeedList(tempSearch));									
	
					
				// 다른 피드를 본 기록이 없는 유저	
				}else {
					// 조회수가 가장 많은 피드를 추천
					feedlist = feedServices.getHightViewFeedList(search);
				}					
			}
				
			
		//추천 카테고리를 선택하지 않은 경우 - 카테고리에 해당하는 이미지를 출력	
		}else {
			feedlist = feedServices.getFeedListFromCategory(search, user);
		}		
		
		map.put("list",feedlist);		
		return map;		
	}
正直、どちらが正解なのか分からない気がします.
会社が見ているコードの多くは、サービスではなくコントローラ上で多くの演算を実行しており、ビューからJAVAコードを削除するのとは異なり、サービスとコントローラをどのような基準で区分すべきか分からない場合が多い.
再利用性を向上させ、ビジネスロジックをビューから分離する設計は、どちらが正しいかを決定するためにさらに考慮する必要があります.

WebSocketのリアルタイムアラートの使用


Instagramを見ると良いコメントで注目したときに相手に注意喚起を送ります個人的にはアラームを見る味(?)私はインスタグラムをよく使っているので、Webプロジェクトでも似たような機能を実現したいと思っていたので、ブラウズでwebsocketを知りました.
(1つの計画サイクルでデータベースに接続して値を取得するポーリングはしたくありません.)
Spring Bootに変更するには、すべてのロジックを変更する必要はありませんが、構成方法を見つけるのに少し時間がかかりました.
    <!-- Websocket -->
    <websocket:handlers>
    	<websocket:mapping handler="echoHandler" path="/echo"/>
    	
    	<!-- Httpsession 의 값을 websession에 넣어준다.  -->
    	<websocket:handshake-interceptors>
 	         <bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
 	    </websocket:handshake-interceptors>     	
    	
    	<websocket:sockjs/>
	</websocket:handlers>
	
	<!-- 핸들러 관리 -->
	<bean id="echoHandler" class="com.VIG.mvc.util.EchoHandler"/>
既存SpringではDispatcher-servletが提供されています.xmlでは、セッション情報を共有するためにHandsharkが適用されますが、springブートに変更するにつれてconfigを定義する別のクラスが作成されます.
package com.vig.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import com.vig.handler.WebSocketHandler;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

	@Autowired
	WebSocketHandler socketHandler;
	
	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		registry.addHandler(socketHandler, "/rt")
				.addInterceptors(new HttpSessionHandshakeInterceptor());
	}
}
既存のアイテムは、ユーザーを区別するためにSessionHandshookによってHttpSessionの情報をWebSocketSessionにインポートします.
最初はSessionHandshokeがどうするのか分からなかったがRegistryだと分かったaddHandler(socketHandler, "/rt")
.addInterceptors(new HttpSessionHandshakeInterceptor()); 1行だけ書くと、HttpSessionの情報がデフォルト値としてWebSocketSessionに入れられます.
既存のロジックでAjaxを使用すると、Webページの更新なしにアラートが送信されるため、アラート表示には、従来のHandlerを使用してWebソケットを適用し、変更する必要はありません.

Webソケットとメッセージを接続するすべての部分がツールバーで実装されます.ツールバーはどのページでも表示されるので、画面を移動するたびにWebコンセントに再接続する必要があります.
この問題を解決するには、単一ページアプリケーション(SPA)を使用してビューを変更して改善することができますが、このターゲットはビューを変更するわけではないため、ビューの階層化が終了しました.
次に、Vision APIの抽出速度の最大目標を変更します.