Compare commits
2 Commits
4f6cc77646
...
b1866a8cdd
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b1866a8cdd | ||
![]() |
d6a59d9f37 |
@ -0,0 +1,88 @@
|
||||
package com.ruoyi.base.controller;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.base.vo.KeywordVO;
|
||||
import com.ruoyi.busi.service.GoogleKeywordService;
|
||||
import com.ruoyi.common.config.GoogleConfig;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.base.domain.BaseSeo;
|
||||
import com.ruoyi.base.service.IBaseSeoService;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
|
||||
import static com.ruoyi.constant.StrConstants.COUNTRY;
|
||||
import static com.ruoyi.constant.StrConstants.LANGUAGE;
|
||||
|
||||
/**
|
||||
* SEO用到的国家和语言Controller
|
||||
*
|
||||
* @author vinjor-m
|
||||
* @date 2025-08-04
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/base/seo")
|
||||
public class BaseSeoController extends BaseController {
|
||||
@Autowired
|
||||
private GoogleConfig googleConfig;
|
||||
@Autowired
|
||||
private IBaseSeoService baseSeoService;
|
||||
@Autowired
|
||||
private GoogleKeywordService googleKeywordService;
|
||||
|
||||
/**
|
||||
* 查询SEO用到的国家和语言列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public AjaxResult list() {
|
||||
LambdaQueryWrapper<BaseSeo> queryWrapper = new LambdaQueryWrapper<BaseSeo>().orderByAsc(BaseSeo::getSort);
|
||||
List<BaseSeo> list = baseSeoService.list(queryWrapper);
|
||||
List<BaseSeo> languageList = list.stream().filter(item -> LANGUAGE.equals(item.getDataType())).collect(Collectors.toList());
|
||||
List<BaseSeo> countryList = list.stream().filter(item -> COUNTRY.equals(item.getDataType())).collect(Collectors.toList());
|
||||
languageList.forEach(item->item.setTitle(item.getTitle()+"("+item.getCn()+")"));
|
||||
countryList.forEach(item->item.setTitle(item.getTitle()+"("+item.getCn()+")"));
|
||||
Map<String, Object> rtnMap = new HashMap<>();
|
||||
rtnMap.put("language", languageList);
|
||||
rtnMap.put("country", countryList);
|
||||
return success(rtnMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取google关键词
|
||||
*
|
||||
* @param language 语言
|
||||
* @param country 国家
|
||||
* @param title 关键词
|
||||
* @param url 产品预览网址
|
||||
* @return com.ruoyi.common.core.domain.AjaxResult
|
||||
* @author vinjor-M
|
||||
* @date 17:51 2025/8/4
|
||||
**/
|
||||
@GetMapping("/getKeywords")
|
||||
public AjaxResult getKeywords(String language, String country, String title, String url) {
|
||||
Map<String,Object> rtnMap = new HashMap<>();
|
||||
rtnMap.put("searchDown",googleConfig.getSearchDown());
|
||||
rtnMap.put("dataList",googleKeywordService.getKeywords(language, country, title, url));
|
||||
return success(rtnMap);
|
||||
}
|
||||
}
|
@ -465,12 +465,6 @@ public class WebController extends BaseController {
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/test")
|
||||
public R<?> test() {
|
||||
googleKeywordService.test();
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航栏接口--所有分类
|
||||
* @author vinjor-M
|
||||
|
@ -0,0 +1,59 @@
|
||||
package com.ruoyi.base.domain;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
import com.ruoyi.common.core.domain.DlBaseEntity;
|
||||
|
||||
/**
|
||||
* SEO用到的国家和语言对象 dl_base_seo
|
||||
*
|
||||
* @author vinjor-m
|
||||
* @date 2025-08-04
|
||||
*/
|
||||
@TableName("dl_base_seo")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class BaseSeo extends DlBaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 主键 */
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private Long id;
|
||||
|
||||
/** 数据类型(language-语言|country-国家) */
|
||||
@Excel(name = "数据类型", readConverterExp = "l=anguage-语言|country-国家")
|
||||
private String dataType;
|
||||
|
||||
/** 排序 */
|
||||
@Excel(name = "排序")
|
||||
private Long sort;
|
||||
|
||||
/** 标题 */
|
||||
@Excel(name = "标题")
|
||||
private String title;
|
||||
|
||||
/** 值 */
|
||||
@Excel(name = "值")
|
||||
private String content;
|
||||
|
||||
/** 描述 */
|
||||
@Excel(name = "描述")
|
||||
private String description;
|
||||
|
||||
/** 图片 */
|
||||
@Excel(name = "图片")
|
||||
private String pic;
|
||||
|
||||
/** 中文 */
|
||||
@Excel(name = "中文")
|
||||
private String cn;
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.ruoyi.base.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.base.domain.BaseSeo;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* SEO用到的国家和语言Mapper接口
|
||||
*
|
||||
* @author vinjor-m
|
||||
* @date 2025-08-04
|
||||
*/
|
||||
@Mapper
|
||||
public interface BaseSeoMapper extends BaseMapper<BaseSeo>
|
||||
{
|
||||
IPage<BaseSeo> queryListPage(@Param("entity") BaseSeo entity, Page<BaseSeo> page);
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.ruoyi.base.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.base.domain.BaseSeo;
|
||||
|
||||
/**
|
||||
* SEO用到的国家和语言Service接口
|
||||
*
|
||||
* @author vinjor-m
|
||||
* @date 2025-08-04
|
||||
*/
|
||||
public interface IBaseSeoService extends IService<BaseSeo>
|
||||
{
|
||||
IPage<BaseSeo> queryListPage(BaseSeo pageReqVO, Page<BaseSeo> page);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.ruoyi.base.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.ruoyi.base.mapper.BaseSeoMapper;
|
||||
import com.ruoyi.base.domain.BaseSeo;
|
||||
import com.ruoyi.base.service.IBaseSeoService;
|
||||
|
||||
/**
|
||||
* SEO用到的国家和语言Service业务层处理
|
||||
*
|
||||
* @author vinjor-m
|
||||
* @date 2025-08-04
|
||||
*/
|
||||
@Service
|
||||
public class BaseSeoServiceImpl extends ServiceImpl<BaseSeoMapper,BaseSeo> implements IBaseSeoService
|
||||
{
|
||||
@Autowired
|
||||
private BaseSeoMapper baseSeoMapper;
|
||||
|
||||
@Override
|
||||
public IPage<BaseSeo> queryListPage(BaseSeo pageReqVO, Page<BaseSeo> page) {
|
||||
return baseSeoMapper.queryListPage(pageReqVO, page);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.ruoyi.base.vo;
|
||||
|
||||
import com.ruoyi.base.domain.BasePics;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 谷歌关键词规划师VO
|
||||
* @author vinjor-M
|
||||
* @date 17:53 2025/8/4
|
||||
**/
|
||||
@Data
|
||||
public class KeywordVO {
|
||||
/**
|
||||
* 关键词
|
||||
**/
|
||||
private String title;
|
||||
/**
|
||||
* 月搜索量
|
||||
**/
|
||||
private Long searchMonth;
|
||||
/**
|
||||
* 竞争程度
|
||||
**/
|
||||
private String competition;
|
||||
}
|
@ -1,18 +1,14 @@
|
||||
package com.ruoyi.busi.service;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.google.ads.googleads.lib.GoogleAdsClient;
|
||||
import com.google.ads.googleads.v20.common.Keyword;
|
||||
import com.google.ads.googleads.v20.common.KeywordAnnotations;
|
||||
import com.google.ads.googleads.v20.common.KeywordConcept;
|
||||
import com.google.ads.googleads.v20.enums.KeywordPlanKeywordAnnotationEnum;
|
||||
import com.google.ads.googleads.v20.enums.KeywordPlanNetworkEnum;
|
||||
import com.google.ads.googleads.v20.enums.KeywordPlanKeywordAnnotationEnum.KeywordPlanKeywordAnnotation;
|
||||
import com.google.ads.googleads.v20.services.*;
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.auth.oauth2.ServiceAccountCredentials;
|
||||
import com.ruoyi.base.vo.KeywordVO;
|
||||
import com.ruoyi.common.config.GoogleConfig;
|
||||
import com.ruoyi.common.config.OssConfig;
|
||||
import lombok.var;
|
||||
import com.ruoyi.constant.CompetitionEnum;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -21,8 +17,6 @@ import org.springframework.util.ResourceUtils;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@ -30,7 +24,19 @@ public class GoogleKeywordService {
|
||||
@Autowired
|
||||
private GoogleConfig googleConfig;
|
||||
|
||||
public void test() {
|
||||
/**
|
||||
* 获取谷歌关键词规划师规划的关键词
|
||||
*
|
||||
* @param language 语言编码
|
||||
* @param country 国家编码,多个英文逗号隔开
|
||||
* @param title 关键词,多个英文逗号分隔
|
||||
* @param url url
|
||||
* @return java.util.List<com.ruoyi.base.vo.KeywordVO>
|
||||
* @author vinjor-M
|
||||
* @date 17:55 2025/8/4
|
||||
**/
|
||||
public List<KeywordVO> getKeywords(String language, String country, String title, String url) {
|
||||
List<KeywordVO> keywordList = new ArrayList<>();
|
||||
try {
|
||||
// 1. 检查文件是否存在
|
||||
File file = ResourceUtils.getFile("file:" + googleConfig.getJsonPath());
|
||||
@ -49,31 +55,42 @@ public class GoogleKeywordService {
|
||||
.build();
|
||||
|
||||
// 2. 设置参数
|
||||
List<String> seedKeywords = Arrays.asList("TRUCK");
|
||||
// 英语
|
||||
String language = "languageConstants/1000";
|
||||
// 美国
|
||||
String geoTarget = "geoTargetConstants/2840";
|
||||
|
||||
List<String> seedKeywords = Arrays.asList(title.split(StrUtil.COMMA));
|
||||
// 语言
|
||||
language = "languageConstants/"+language;
|
||||
// 国家/地区
|
||||
List<String> geoTargetList = new ArrayList<>();
|
||||
for (String item:country.split(StrUtil.COMMA)){
|
||||
geoTargetList.add("geoTargetConstants/"+item);
|
||||
}
|
||||
// 3. 获取关键词提示
|
||||
List<GenerateKeywordIdeaResult> keywordIdeas = generateKeywordIdeas(
|
||||
googleAdsClient, googleConfig.getCustomerId(), seedKeywords, language, geoTarget);
|
||||
googleAdsClient, googleConfig.getCustomerId(), seedKeywords, language, geoTargetList,url);
|
||||
|
||||
// 4. 输出结果
|
||||
for (GenerateKeywordIdeaResult idea : keywordIdeas) {
|
||||
// 获取关键词文本
|
||||
String text = idea.getText();
|
||||
KeywordVO keywordVO = new KeywordVO();
|
||||
keywordVO.setTitle(text);
|
||||
// 获取关键词指标(可选)
|
||||
if (idea.hasKeywordIdeaMetrics()) {
|
||||
long avgMonthlySearches = idea.getKeywordIdeaMetrics().getAvgMonthlySearches();
|
||||
String competition = String.valueOf(idea.getKeywordIdeaMetrics().getCompetition());
|
||||
System.out.println("找到的关键词文本:" + text+";月搜索量:" + avgMonthlySearches+";竞争程度:" + competition);
|
||||
if(avgMonthlySearches>=googleConfig.getSearchDown()){
|
||||
//大于要求的最低月搜索量,返回
|
||||
keywordVO.setSearchMonth(avgMonthlySearches);
|
||||
keywordVO.setCompetition(CompetitionEnum.getTitleByCode(competition).getTitle());
|
||||
keywordList.add(keywordVO);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.printf("初始化 Google Ads 客户端失败: %s%n", e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
// 倒序排列
|
||||
keywordList.sort((o1, o2) -> Long.compare(o2.getSearchMonth(), o1.getSearchMonth()));
|
||||
return keywordList;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,7 +100,8 @@ public class GoogleKeywordService {
|
||||
* @param customerId 客户ID
|
||||
* @param seedKeywords 种子关键词列表
|
||||
* @param language 语言常量 (如 "languageConstants/1000" 表示英语)
|
||||
* @param geoTarget 地理位置目标常量 (如 "geoTargetConstants/2840" 表示美国)
|
||||
* @param geoTargets 地理位置目标常量 (如 "geoTargetConstants/2840" 表示美国)
|
||||
* @param url 网址
|
||||
* @return 关键词提示列表
|
||||
* @throws IOException 如果API调用失败
|
||||
*/
|
||||
@ -92,24 +110,36 @@ public class GoogleKeywordService {
|
||||
long customerId,
|
||||
List<String> seedKeywords,
|
||||
String language,
|
||||
String geoTarget) throws IOException {
|
||||
|
||||
List<String> geoTargets,String url) throws IOException {
|
||||
List<GenerateKeywordIdeaResult> allKeywordIdeas = new ArrayList<>();
|
||||
//分页token
|
||||
String nextPageToken = null;
|
||||
|
||||
KeywordAndUrlSeed seed = KeywordAndUrlSeed.newBuilder()
|
||||
//组装关键词请求体
|
||||
KeywordAndUrlSeed urlSeed = null;
|
||||
KeywordSeed seed = null;
|
||||
if(StringUtils.isNotEmpty(url)){
|
||||
urlSeed = KeywordAndUrlSeed.newBuilder()
|
||||
.addAllKeywords(seedKeywords)
|
||||
.setUrl("https://www.cdtruck.com/truck/dump-truck/china-sinotruk-howo-19m3-6x4-cheap-336hp-10.html") // 提供相关 URL
|
||||
.setUrl(url)
|
||||
.build();
|
||||
}else{
|
||||
seed = KeywordSeed.newBuilder()
|
||||
.addAllKeywords(seedKeywords)
|
||||
.build();
|
||||
}
|
||||
do {
|
||||
try (KeywordPlanIdeaServiceClient client =
|
||||
googleAdsClient.getLatestVersion().createKeywordPlanIdeaServiceClient()) {
|
||||
GenerateKeywordIdeasRequest.Builder requestBuilder = GenerateKeywordIdeasRequest.newBuilder()
|
||||
.setCustomerId(Long.toString(customerId))
|
||||
.setLanguage(String.valueOf(1000))
|
||||
.addAllGeoTargetConstants(Collections.singletonList(geoTarget))
|
||||
.setKeywordPlanNetwork(KeywordPlanNetworkEnum.KeywordPlanNetwork.GOOGLE_SEARCH_AND_PARTNERS)
|
||||
.setKeywordAndUrlSeed(seed);
|
||||
.setLanguage(language)
|
||||
.addAllGeoTargetConstants(geoTargets)
|
||||
.setKeywordPlanNetwork(KeywordPlanNetworkEnum.KeywordPlanNetwork.GOOGLE_SEARCH_AND_PARTNERS);
|
||||
if(StringUtils.isNotEmpty(url)){
|
||||
requestBuilder.setKeywordAndUrlSeed(urlSeed);
|
||||
}else{
|
||||
requestBuilder.setKeywordSeed(seed);
|
||||
}
|
||||
// 仅在非首次请求时设置 pageToken
|
||||
if (nextPageToken != null) {
|
||||
requestBuilder.setPageToken(nextPageToken);
|
||||
@ -118,10 +148,9 @@ public class GoogleKeywordService {
|
||||
GenerateKeywordIdeaResponse response = client.generateKeywordIdeasCallable().call(request);
|
||||
allKeywordIdeas.addAll(response.getResultsList());
|
||||
nextPageToken = response.getNextPageToken();
|
||||
System.out.println("分页token:"+nextPageToken);
|
||||
}
|
||||
} while (StringUtils.isNotEmpty(nextPageToken));
|
||||
System.out.println("关键词总数:"+allKeywordIdeas.size());
|
||||
System.out.println("关键词总数:" + allKeywordIdeas.size());
|
||||
return allKeywordIdeas;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,58 @@
|
||||
package com.ruoyi.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 竞争程度枚举值
|
||||
*
|
||||
* @author vinjor-m
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum CompetitionEnum {
|
||||
/**
|
||||
*UNSPECIFIED-未知
|
||||
*/
|
||||
UNSPECIFIED("UNSPECIFIED","未知"),
|
||||
/**
|
||||
*UNKNOWN-未知
|
||||
*/
|
||||
UNKNOWN("UNKNOWN","未知"),
|
||||
/**
|
||||
*LOW-低
|
||||
*/
|
||||
LOW("LOW","低"),
|
||||
/**
|
||||
*MEDIUM-中
|
||||
*/
|
||||
MEDIUM("MEDIUM","中"),
|
||||
/**
|
||||
*HIGH-高
|
||||
*/
|
||||
HIGH("HIGH","高");
|
||||
|
||||
/**
|
||||
* code
|
||||
*/
|
||||
private String code;
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* 根据code返回对应的枚举
|
||||
* @author vinjor-M
|
||||
* @date 14:23 2024/10/16
|
||||
* @param code code
|
||||
**/
|
||||
public static CompetitionEnum getTitleByCode(String code) {
|
||||
for (CompetitionEnum thisEnum : CompetitionEnum.values()) {
|
||||
if (thisEnum.getCode().equalsIgnoreCase(code)) {
|
||||
// 找到对应的枚举
|
||||
return thisEnum;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("无效的code:" + code);
|
||||
}
|
||||
}
|
@ -27,4 +27,13 @@ public class StrConstants {
|
||||
* 取权重最高的前N个词
|
||||
*/
|
||||
public static final int TOP_N = 3;
|
||||
|
||||
/**
|
||||
* 语言
|
||||
*/
|
||||
public static final String LANGUAGE = "language";
|
||||
/**
|
||||
* 国家
|
||||
*/
|
||||
public static final String COUNTRY = "country";
|
||||
}
|
||||
|
@ -153,7 +153,12 @@ aliyun:
|
||||
#google ads配置
|
||||
google:
|
||||
ads:
|
||||
customer-id: 4577179829
|
||||
developer-token: qwFA3dGfOGqEhSEHlCMorA
|
||||
json-path: /www/wwwroot/nuxt/test.json
|
||||
#经理账号id
|
||||
customer-id: 3052342727
|
||||
#开发者令牌
|
||||
developer-token: 7e_-FWCPRjWc16zr8uaUDg
|
||||
#服务账号秘钥文件路径
|
||||
json-path: /www/wwwroot/nuxt/chengda.json
|
||||
#月搜索量下限
|
||||
search-down: 500
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "chengda-466307",
|
||||
"private_key_id": "c4f38952fb0922ff625a85d364543f415f93d03d",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCRIuvHsOaOnXv5\nlLl3R0fLrWjd8fk6tDv1bIJFpC2xfFem2rYaG1IFJVfnGgCAL7P4j6tVn0j684dL\n2wb4BRJjyK4EDYBLkugc3BWNnpt6z80f8+U39/QtDwRBJqQGvRD4LGhm5dDrAWhf\ngeiCKVC+3TGDGDwkt4JjuZQ6/GSrvX9aSaLyT5PlpYSvxH2tetb5rY6LdeAR9wS1\n0dtFD0jIxYHm96vWpe1LLhtw+28thxYeR7VFOZf6HzJLDDOt4hYbHoo+DAKaPIyC\n6+CepAcE13WWn7ooPGjh8JAGPqs2JpqR6ujFrvYXAto3UvpXSV0W6h1gRv9AD3SW\n33Te4KYzAgMBAAECggEAEu74zkCsGx+9TBZUSZSxb4csPoPyDO/1QHOK3RXpZhWA\nA8LVbbtxrD0uZfYU6aQPeNYZNl7EsQQy+rjUhGd4+i50URAH1BQSlq05XJO72b4h\nFtGE7hO5NWWXmv40+LISdCWq6v2BDx9MY+U5FT3ZjESj0GPJeMq9xk+v4DAL3AF2\nqsXsL/GhSLTyIWufIaxwUPFfumVVqjH3r9B1YqY+0Z//E/2I9W3NOSQ83JzI1osw\nQ5ybQdQ9zQptlr/kh9sb+vtgJ4+7h6RQ6Xcyy6OsW4Kg6Ho7eRTBDIytv9o7Ef+h\nVYwV6OjonAcWcbfCY2TGEO2DDJJbkZZwRby2WE5jkQKBgQDMQt/XfWCI2EVPNwpn\nl6gFY7vWcXSO+dkEloFgIpbxiJTmZTRmWPnpqqEaxUNo2KCxyuUoOCC7B+D6AdRI\nBZfxaFtQe+f71oocnWbjNtzLvW8cnZouHI1pT+HXWO84aq7fh4lkYhXsEZkhu2Ce\nV1nGjSVboe/OJ+Z9oCNVQxtlsQKBgQC15idlkFZbCR3zBDmc9LRDC1iZXyhBlSva\nT30e1M7sOO8lN2Uw825XaP4CKE70jOxL5eLkSvXALWOdeVZ79iclAoz+iE8FsLPf\nbUOWAEuUtsgyyrmkcKU/ddvtvEwsBFqZ0CxxqOCDXj+uXoGifyFLigkl3w5wvY0s\njpe1wipvIwKBgQCUvvpc5XugC8ZlSlK0X5dG3XsTTamw2Lc2BRgP1wCOwYSVRwvi\noFbV16DcatyNBHv5HSTFpiIHsVQfG6foDtK4ROOCd8jW90O6VNFxEym04K2CbC6z\n96zvDPIMrUH/lojkVMIzrM4EDEi0bMyOYlQJKA4VbZbBTQMnZq90Tpsr8QKBgQCr\nd/k9aZGuIWsFEb+JsLdY2BI+ChC1ufvrwLDO5obk8UqmR5DxUxh597QyrnK3XzzE\n00FOOUduUJst8BrRohoGbmAg9LehQpBdFu/2L/MPjjosfyP+2l079EtM0QrxF22c\nvzuWLT7vN2JKajZDyxnEzquO8rT3HAg/r29d3FoKBwKBgQCM4BNOTBT10gCPKQBx\nApULMInlweLr393rc17cJHSSLmL0z26oIfpJYVf+AbV4sLKE9HaCmwxTiR/frt9f\nEhAaOsWfprl/CFC3kcF/Vndey2v9Nlwdzl7//8c0O8ITlqt+OpK3giOUquMWuG8i\nFU9AwpseRhEp5uHZ5e1wKgsEsA==\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "chengdatest@chengda-466307.iam.gserviceaccount.com",
|
||||
"client_id": "108513526311014104962",
|
||||
"project_id": "chengda-467707",
|
||||
"private_key_id": "a9a538af8b2e1e3a41f8bc6265200a432450dd00",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCaoBGTyINWYh5o\nFSmaGJ33q8wRIevL+GC6oqywzdyvk7in5MKpkGtKnx4yy8Cy0TSHp5yAsoXtblWV\n1TrrV7hia8IizB6QxkZQbcwwPz12shH7HN65m4lOdhsEjsSPZogYIwvDDtkXsAyl\nB+ALfYb+DZpX2aTST+1fvtTc+3PdrfxXBmBqmwJeTaQOVBk10BW+vDL9Y8TxuvHS\nYWbix/PJDQEpJFcQI/1shgiyeNkvBQJAxUHgb58EncukAD3F1W7wLLEJtE+5cnGU\nCH1KNHt+ymmZ/TJ1hdf/BvSjtHTgTYSM/ViEQZ7aTj1RZ1xueflSt2Pzqq84GMP3\nOJaCgcObAgMBAAECggEAC6v3h7NEDro7V1n5mPMwwWhLcCOiW/Q0wQHUjUgM14gM\nEUhYVVpcbvaR3f25XwYhs/e3/ZL8Pw7sEPJWjztA5NUDg4Lq77y3xAx1tFKhvhr0\n9oKYYWRXOf/6TB7GwZhYv5GtqpytUHdujOqKQ/PGw9idBPKd2W5FGcwa309v0JTa\ndhiaUAxi19QJNwqxDHn665EBfAHqZE76h0TR2fXiQ8XfH0T7EQuTuSI4sAD1MYaS\nU/0YnEi9eulCVJNpD5CDVFjL1C5Oa/LkMTo/SubaAd7pTY9Uo3LOrykikRBozrMc\nisObanEZDyi+y435ZMfH4ikywJq7cN4Xnlis06nBcQKBgQDTv0JaX0ze8v6fiwaZ\nrbHvEehAyRvGGaY0/mxDYM9Ioq80aIAG5/Ogh3WGDyrynz4zCu1mqjRqterzpY+H\nSP6fZzhTi7lUHGVa4QUb4QlSkDWV0BED8we4AzXnBOHmto+aMwm1bi0O3qWcbWDk\nOacLIoAOhNKOn4IAzaHWAkgH6QKBgQC68LfXb9EEGafeHIjz2o08EhqHSoMs1kAp\nvu39vh3qdMIlSXWrmGXvQS4EJyBMyq24DLufUbEMRaf2yvbCpl0NRzo3dUeXXQWh\nLBT/9erKF10IjEHd8t4PUoE4JbtuLUPUAKBCx5pbtCcAMi1wWm+kcx9uLdKw/sPC\nKBKuRJTA4wKBgA4yYpaAkTzZeezke1rOZIGs56+ATFZp3dTrwgJ6eStbjPeskfjk\nkFcQ/fYxCiYeOyNc9jHN85m7/La9QPRHFptFZmdjlNdo1/rR32/RFLjuZklXG+zx\n3HCf9ns4vpSjZNln1pzNeEPo9Z2ZT8e9fc+nToKqsRtS4np/Tvx0RrQRAoGAB/Pd\nnkifd7Evtv83xEfz4i5S+pZs5idnK193+GdyFltJNxTc8KPXEkqPjpWrhhjJMx83\njBuJkKAV+SXBQZ6aLsps65cNqJ7IcobRjeIETvyXdJmi47JFSBmWbbENPC6oyAgO\nyUh8hJIxZoy76h+uyCwtlh5U7DqqUUQrJ0a7nUkCgYBoRunRKHNRycFlMFW2rLf+\n+ayC7fIPvLiKzYg2xAHIG/ykTGmtxrqKiVQ909FyVN7k9Rbyg9ZVZdkCqG/Br7iS\nVOF7xD1SCma3pdupRP4GIQSxVZVvUo48xzXNUFWXg4bjzMEgIErG12zL286blVPi\nxMz4TKijJq2YR67kdAEzDw==\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "shandongchengda@chengda-467707.iam.gserviceaccount.com",
|
||||
"client_id": "107299671168418329002",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/chengdatest%40chengda-466307.iam.gserviceaccount.com",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/shandongchengda%40chengda-467707.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.base.mapper.BaseSeoMapper">
|
||||
|
||||
<resultMap type="BaseSeo" id="BaseSeoResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="dataType" column="data_type" />
|
||||
<result property="sort" column="sort" />
|
||||
<result property="title" column="title" />
|
||||
<result property="content" column="content" />
|
||||
<result property="description" column="description" />
|
||||
<result property="pic" column="pic" />
|
||||
<result property="cn" column="cn" />
|
||||
<result property="creator" column="creator" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updater" column="updater" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectBaseSeoVo">
|
||||
select id, data_type, sort, title, content, description, pic, cn, creator, create_time, updater, update_time, del_flag from dl_base_seo
|
||||
</sql>
|
||||
|
||||
<select id="queryListPage" parameterType="BaseSeo" resultMap="BaseSeoResult">
|
||||
<include refid="selectBaseSeoVo"/>
|
||||
<where>
|
||||
<if test="entity.dataType != null and entity.dataType != ''"> and data_type = #{entity.dataType}</if>
|
||||
<if test="entity.sort != null "> and sort = #{entity.sort}</if>
|
||||
<if test="entity.title != null and entity.title != ''"> and title = #{entity.title}</if>
|
||||
<if test="entity.content != null and entity.content != ''"> and content = #{entity.content}</if>
|
||||
<if test="entity.description != null and entity.description != ''"> and description = #{entity.description}</if>
|
||||
<if test="entity.pic != null and entity.pic != ''"> and pic = #{entity.pic}</if>
|
||||
<if test="entity.cn != null and entity.cn != ''"> and cn = #{entity.cn}</if>
|
||||
</where>
|
||||
</select>
|
||||
</mapper>
|
@ -15,6 +15,7 @@ public class GoogleConfig {
|
||||
private Long customerId;
|
||||
private String developerToken;
|
||||
private String jsonPath;
|
||||
private Long searchDown;
|
||||
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ VUE_APP_TITLE = 成事达管理平台
|
||||
ENV = 'development'
|
||||
|
||||
# 成事达管理平台/开发环境
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
VUE_APP_BASE_API = 'http://127.0.0.1:8099'
|
||||
|
||||
# 路由懒加载
|
||||
VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
||||
|
19
dl_vue/src/api/base/seo.js
Normal file
19
dl_vue/src/api/base/seo.js
Normal file
@ -0,0 +1,19 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询SEO用到的国家和语言列表
|
||||
export function listSeo(query) {
|
||||
return request({
|
||||
url: '/base/seo/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询关键词规划
|
||||
export function getKeywords(query) {
|
||||
return request({
|
||||
url: '/base/seo/getKeywords',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
@ -6,9 +6,90 @@
|
||||
<el-button type="success" @click="saveTmp">暂 存</el-button>
|
||||
<el-button type="primary" @click="submitForm">发 布</el-button>
|
||||
<el-button type="warning" @click="checkContent">相似度检测</el-button>
|
||||
<el-button @click="toggleSideBar">{{ showKeywords ? '关闭' : '打开' }}关键词推荐</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider></el-divider>
|
||||
<!-- 表单区域-->
|
||||
<el-row>
|
||||
<el-col v-if="showKeywords" :span="8">
|
||||
<el-form :model="formSeo" ref="queryForm" :rules="rulesSeo" size="small" :inline="true" label-width="110px">
|
||||
<el-form-item label="基准词" prop="title">
|
||||
<template v-slot:label>
|
||||
<span>基准词</span>
|
||||
<el-tooltip class="item" effect="dark" content="请输入您的产品名称,尽量不要过于具体或泛化" placement="bottom">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<div class="dl-flex-column">
|
||||
<el-input style="width: 150px" v-model="formSeo.title" placeholder="请输入基准词"/>
|
||||
<div class="dl-add-catg" @click="useProdName">使用产品名称</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="语言" prop="language">
|
||||
<el-select style="width: 150px" v-model="formSeo.language" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in languageArray"
|
||||
:key="item.content"
|
||||
:label="item.title"
|
||||
:value="item.content"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="结合产品内容" prop="needUrl">
|
||||
<template v-slot:label>
|
||||
<span>结合产品内容</span>
|
||||
<el-tooltip class="item" effect="dark" content="请确保已经录入产品内容并保存!" placement="bottom">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-switch
|
||||
v-model="formSeo.needUrl"
|
||||
>
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="国家/地区" prop="country">
|
||||
<el-select v-model="formSeo.country" multiple filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in countryArray"
|
||||
:key="item.content"
|
||||
:label="item.title"
|
||||
:value="item.content"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" :loading="loading" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-tag type="warning" v-if="keywordsList.length>0">共抓取到{{keywordsList.length}}个关键词,月搜索量低于{{searchDown}}的关键词已为您自动忽略</el-tag>
|
||||
<el-table v-loading="loading" :data="keywordsList" max-height="500">
|
||||
<el-table-column type="index" width="60" label="序号" align="center"></el-table-column>
|
||||
<el-table-column label="关键词" align="center" prop="title"/>
|
||||
<el-table-column label="月搜索量" align="center" prop="searchMonth" width="100">
|
||||
<template slot-scope="scope">
|
||||
<countTo :startVal="0" :endVal="scope.row.searchMonth" :duration="1000"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="竞争程度" align="center" prop="competition" width="80"/>
|
||||
<el-table-column width="60" label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)">
|
||||
<el-button size="mini" type="text">使用</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="handleTitle">添加至title</el-dropdown-item>
|
||||
<el-dropdown-item command="handleKeyword">添加至keyword</el-dropdown-item>
|
||||
<el-dropdown-item command="handleDescription">添加至description</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
<el-col :span="showKeywords?16:24">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="150px">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
@ -99,6 +180,8 @@
|
||||
<editor v-model="form.content" :min-height="192"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider></el-divider>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
@ -115,7 +198,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import countTo from 'vue-count-to';
|
||||
import { listCategory } from '@/api/busi/category'
|
||||
import { listSeo ,getKeywords} from '@/api/base/seo'
|
||||
import Hamburger from '@/components/Hamburger'
|
||||
import {
|
||||
listProdNew,
|
||||
getProdNew,
|
||||
@ -133,7 +219,7 @@ import SimilarityProduct from './similarityProduct'
|
||||
|
||||
export default {
|
||||
name: 'prodForm',
|
||||
components: { SimilarityProduct, Treeselect, selectPic },
|
||||
components: { SimilarityProduct, Treeselect, selectPic, Hamburger , countTo},
|
||||
data() {
|
||||
return {
|
||||
//光标位置--简介
|
||||
@ -171,6 +257,22 @@ export default {
|
||||
//当前上传的所有图片
|
||||
fileList: []
|
||||
},
|
||||
formSeo: {
|
||||
title: '',
|
||||
needUrl: true,
|
||||
language: '',
|
||||
country:"",
|
||||
},
|
||||
languageArray: [],
|
||||
countryArray: [],
|
||||
//是否显示关键词提示区域
|
||||
showKeywords: true,
|
||||
// 遮罩层
|
||||
loading: false,
|
||||
// 关键词列表数据
|
||||
keywordsList: [],
|
||||
//月搜索量最低下限
|
||||
searchDown:null,
|
||||
//相似产品
|
||||
similarityList: [],
|
||||
// 表单校验
|
||||
@ -190,11 +292,23 @@ export default {
|
||||
description: [
|
||||
{ required: true, message: '请输入产品简介', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
rulesSeo:{
|
||||
title: [
|
||||
{ required: true, message: '请输入基准词', trigger: 'blur' }
|
||||
],
|
||||
language: [
|
||||
{ required: true, message: '请选择语言', trigger: 'blur' }
|
||||
],
|
||||
country: [
|
||||
{ required: true, message: '请选择国家', trigger: 'blur' }
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initCatg()
|
||||
this.initSeo()
|
||||
if (this.$route.query.id) {
|
||||
if (this.$route.query.type) {
|
||||
//添加类似
|
||||
@ -208,6 +322,64 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
useProdName(){
|
||||
this.formSeo.title = this.form.title
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.$refs['queryForm'].validate(valid => {
|
||||
if (valid) {
|
||||
this.loading=true
|
||||
let query = JSON.parse(JSON.stringify(this.formSeo))
|
||||
let countryStr = query.country.join()
|
||||
query.country = countryStr
|
||||
if(query.needUrl){
|
||||
query.url="https://www.cdtruck.com/truck/dump-truck/china-sinotruk-howo-19m3-6x4-cheap-336hp-10.html"
|
||||
}
|
||||
getKeywords(query).then(response => {
|
||||
this.keywordsList = response.data.dataList
|
||||
this.searchDown = response.data.searchDown
|
||||
this.loading=false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm");
|
||||
this.handleQuery();
|
||||
},
|
||||
toggleSideBar() {
|
||||
this.showKeywords = !this.showKeywords
|
||||
},
|
||||
// 更多操作触发
|
||||
handleCommand(command, row) {
|
||||
switch (command) {
|
||||
case 'handleTitle':
|
||||
if(this.form.prodTitle){
|
||||
this.form.prodTitle+=","+row.title
|
||||
}else{
|
||||
this.form.prodTitle=row.title
|
||||
}
|
||||
break
|
||||
case 'handleKeyword':
|
||||
if(this.form.prodKeyword){
|
||||
this.form.prodKeyword+=","+row.title
|
||||
}else{
|
||||
this.form.prodKeyword=row.title
|
||||
}
|
||||
break
|
||||
case 'handleDescription':
|
||||
if(this.form.prodDescription){
|
||||
this.form.prodDescription+=","+row.title
|
||||
}else{
|
||||
this.form.prodDescription=row.title
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
goCatgView() {
|
||||
this.$router.push({ path: '/busi/category' })
|
||||
},
|
||||
@ -220,6 +392,12 @@ export default {
|
||||
this.catgOptions = response.data
|
||||
})
|
||||
},
|
||||
initSeo(){
|
||||
listSeo({ }).then(response => {
|
||||
this.languageArray = response.data.language
|
||||
this.countryArray = response.data.country
|
||||
})
|
||||
},
|
||||
/** 转换部门数据结构 */
|
||||
normalizer(node) {
|
||||
if (node.children && !node.children.length) {
|
||||
@ -363,10 +541,24 @@ export default {
|
||||
width: 110px;
|
||||
text-align: center;
|
||||
color: #1890ff;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.dl-tooltip-text {
|
||||
color: #495060;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.hamburger-container {
|
||||
line-height: 46px;
|
||||
height: 100%;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
transition: background .3s;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.hamburger-container:hover {
|
||||
background: rgba(0, 0, 0, .025)
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user