Feature #5 申请友链时自动抓取网页信息 #6

Merged
xiaohai2271 merged 21 commits from feature-#5 into dev 2020-08-01 21:24:47 +08:00
20 changed files with 543 additions and 124 deletions

View File

@@ -72,13 +72,15 @@ CREATE TABLE `comment`
CREATE TABLE `links` CREATE TABLE `links`
( (
`l_id` bigint(20) primary key auto_increment, `l_id` bigint(20) primary key auto_increment,
`l_name` varchar(255) COLLATE utf8mb4_unicode_ci not null comment '友站名称', `l_name` varchar(255) COLLATE utf8mb4_unicode_ci not null comment '友站名称',
`l_is_open` boolean default true comment '是否公开', `l_is_open` boolean default true comment '是否公开',
`l_url` varchar(255) not null comment '首页地址', `l_url` varchar(255) unique not null comment '首页地址',
`l_icon_path` varchar(255) not null comment '友链的icon地址', `l_icon_path` varchar(255) not null comment '友链的icon地址',
`l_desc` varchar(255) COLLATE utf8mb4_unicode_ci not null comment '友链的说明描述', `l_desc` varchar(255) COLLATE utf8mb4_unicode_ci not null comment '友链的说明描述',
`is_delete` boolean not null default false comment '该数据是否被删除' `is_delete` boolean not null default false comment '该数据是否被删除',
`l_email` varchar(255) comment '网站管理员的邮箱',
`l_notification` boolean default false comment '是否通知了'
) comment '友站表'; ) comment '友站表';
CREATE TABLE `visitor` CREATE TABLE `visitor`

41
pom.xml
View File

@@ -148,6 +148,29 @@
<artifactId>jjwt</artifactId> <artifactId>jjwt</artifactId>
<version>0.9.1</version> <version>0.9.1</version>
</dependency> </dependency>
<!-- OkHttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.8.0</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>1.3.72</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>2.42.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -157,21 +180,15 @@
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.jacoco</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>jacoco-maven-plugin</artifactId> <artifactId>kotlin-maven-plugin</artifactId>
<version>0.7.9</version> <version>1.3.72</version>
<executions> <executions>
<execution> <execution>
<id>pre-unit-test</id> <id>compile</id>
<phase>process-sources</phase>
<goals> <goals>
<goal>prepare-agent</goal> <goal>compile</goal>
</goals>
</execution>
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>

View File

@@ -26,7 +26,7 @@ public class CorsConfig {
config.addAllowedOrigin("https://celess.cn"); config.addAllowedOrigin("https://celess.cn");
config.addAllowedOrigin("https://www.celess.cn"); config.addAllowedOrigin("https://www.celess.cn");
// 本地调试时的跨域 // 本地调试时的跨域
if ("dev".equals(activeModel)) { if (!"prod".equals(activeModel)) {
config.addAllowedOrigin("http://localhost:4200"); config.addAllowedOrigin("http://localhost:4200");
config.addAllowedOrigin("http://127.0.0.1:4200"); config.addAllowedOrigin("http://127.0.0.1:4200");
} }

View File

@@ -26,7 +26,7 @@ public class SwaggerConfig {
@Bean @Bean
public Docket createRestApi() { public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2) return new Docket(DocumentationType.SWAGGER_2)
.enable("dev".equals(environment)) .enable(!"prod".equals(environment))
.apiInfo(apiInfo()) .apiInfo(apiInfo())
.select() .select()
.apis(RequestHandlerSelectors.basePackage("cn.celess.blog")) .apis(RequestHandlerSelectors.basePackage("cn.celess.blog"))

View File

@@ -135,8 +135,6 @@ public class CommonController {
* FUCK !!! * FUCK !!!
* *
* @param file 文件 * @param file 文件
* @return
* @throws IOException
*/ */
@PostMapping("/imgUpload") @PostMapping("/imgUpload")
public void upload(HttpServletRequest request, HttpServletResponse response, @RequestParam("editormd-image-file") MultipartFile file) throws IOException { public void upload(HttpServletRequest request, HttpServletResponse response, @RequestParam("editormd-image-file") MultipartFile file) throws IOException {
@@ -158,6 +156,7 @@ public class CommonController {
return; return;
} }
String fileName = file.getOriginalFilename(); String fileName = file.getOriginalFilename();
assert fileName != null;
String mime = fileName.substring(fileName.lastIndexOf(".")); String mime = fileName.substring(fileName.lastIndexOf("."));
if (".png".equals(mime.toLowerCase()) || ".jpg".equals(mime.toLowerCase()) || if (".png".equals(mime.toLowerCase()) || ".jpg".equals(mime.toLowerCase()) ||
".jpeg".equals(mime.toLowerCase()) || ".bmp".equals(mime.toLowerCase())) { ".jpeg".equals(mime.toLowerCase()) || ".bmp".equals(mime.toLowerCase())) {
@@ -178,11 +177,7 @@ public class CommonController {
public Response bingPic() { public Response bingPic() {
JSONObject imageObj; JSONObject imageObj;
try { imageObj = JSONObject.fromObject(HttpUtil.get("https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN"));
imageObj = JSONObject.fromObject(HttpUtil.get("https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN"));
} catch (IOException e) {
return Response.failure(null);
}
JSONArray jsonArray = imageObj.getJSONArray("images"); JSONArray jsonArray = imageObj.getJSONArray("images");
String imageName = jsonArray.getJSONObject(0).getString("url"); String imageName = jsonArray.getJSONObject(0).getString("url");
return Response.success("https://cn.bing.com" + imageName); return Response.success("https://cn.bing.com" + imageName);

View File

@@ -3,6 +3,7 @@ package cn.celess.blog.controller;
import cn.celess.blog.enmu.ResponseEnum; import cn.celess.blog.enmu.ResponseEnum;
import cn.celess.blog.entity.PartnerSite; import cn.celess.blog.entity.PartnerSite;
import cn.celess.blog.entity.Response; import cn.celess.blog.entity.Response;
import cn.celess.blog.entity.request.LinkApplyReq;
import cn.celess.blog.entity.request.LinkReq; import cn.celess.blog.entity.request.LinkReq;
import cn.celess.blog.exception.MyException; import cn.celess.blog.exception.MyException;
import cn.celess.blog.service.MailService; import cn.celess.blog.service.MailService;
@@ -69,29 +70,12 @@ public class LinksController {
} }
@PostMapping("/apply") @PostMapping("/apply")
public Response apply(@RequestParam("name") String name, public Response apply(@RequestBody() LinkApplyReq linkApplyReq) {
@RequestParam("url") String url) { return Response.success(partnerSiteService.apply(linkApplyReq));
// TODO :: 弃用发送邮件的方式。 }
if (name == null || name.replaceAll(" ", "").isEmpty()) {
return Response.response(ResponseEnum.PARAMETERS_ERROR, null); @PostMapping("/reapply")
} public Response reapply(@RequestParam("key") String key) {
if (!RegexUtil.urlMatch(url)) { return Response.success(partnerSiteService.reapply(key));
return Response.response(ResponseEnum.PARAMETERS_URL_ERROR, null);
}
String applyTimeStr = redisUtil.get(request.getRemoteAddr() + "-Apply");
int applyTime = 0;
if (applyTimeStr != null) {
applyTime = Integer.parseInt(applyTimeStr);
}
if (applyTime == 10) {
throw new MyException(ResponseEnum.FAILURE.getCode(), "申请次数已达10次请2小时后重试");
}
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject("友链申请:" + name);
message.setTo("a@celess.cn");
message.setText("name:" + name + "\nurl:" + url + "\n" + DateFormatUtil.getNow());
Boolean send = mailService.send(message);
redisUtil.setEx(request.getRemoteAddr() + "-Apply", applyTime + 1 + "", 2, TimeUnit.HOURS);
return send ? Response.success("") : Response.failure("");
} }
} }

View File

@@ -58,6 +58,9 @@ public enum ResponseEnum {
DATA_HAS_EXIST(7020, "数据已存在"), DATA_HAS_EXIST(7020, "数据已存在"),
//其他 //其他
APPLY_LINK_NO_ADD_THIS_SITE(7200, "暂未在您的网站中抓取到本站链接"),
DATA_EXPIRED(7300, "数据过期"),
CANNOT_GET_DATA(7400, "暂无法获取到数据"),
//提交更新之前,没有获取数据/, //提交更新之前,没有获取数据/,
DID_NOT_GET_THE_DATA(8020, "非法访问"), DID_NOT_GET_THE_DATA(8020, "非法访问"),

View File

@@ -25,6 +25,10 @@ public class PartnerSite {
private Boolean delete = false; private Boolean delete = false;
private String email;
private Boolean notification = true;
public PartnerSite() { public PartnerSite() {
} }

View File

@@ -0,0 +1,17 @@
package cn.celess.blog.entity.request;
import lombok.Data;
/**
* @author : xiaohai
* @date : 2020/07/31 20:50
*/
@Data
public class LinkApplyReq {
private String name;
private String email;
private String url;
private String linkUrl;
private String desc;
private String iconPath;
}

View File

@@ -39,8 +39,9 @@ public class ExceptionHandle {
public Response handle(Exception e) { public Response handle(Exception e) {
//自定义错误 //自定义错误
if (e instanceof MyException) { if (e instanceof MyException) {
logger.debug("返回了自定义的exception,[code={},msg={}]", ((MyException) e).getCode(), e.getMessage()); MyException exception = (MyException) e;
return new Response(((MyException) e).getCode(), e.getMessage(), null); logger.debug("返回了自定义的exception,[code={},msg={},result={}]", exception.getCode(), e.getMessage(), exception.getResult());
return new Response(exception.getCode(), e.getMessage(), exception.getResult());
} }
//请求路径不支持该方法 //请求路径不支持该方法
if (e instanceof HttpRequestMethodNotSupportedException) { if (e instanceof HttpRequestMethodNotSupportedException) {

View File

@@ -1,13 +1,16 @@
package cn.celess.blog.exception; package cn.celess.blog.exception;
import cn.celess.blog.enmu.ResponseEnum; import cn.celess.blog.enmu.ResponseEnum;
import lombok.Data;
/** /**
* @author : xiaohai * @author : xiaohai
* @date : 2019/03/28 16:56 * @date : 2019/03/28 16:56
*/ */
@Data
public class MyException extends RuntimeException { public class MyException extends RuntimeException {
private int code; private int code;
private Object result;
public MyException(int code, String msg) { public MyException(int code, String msg) {
super(msg); super(msg);
@@ -24,11 +27,9 @@ public class MyException extends RuntimeException {
this.code = e.getCode(); this.code = e.getCode();
} }
public int getCode() { public MyException(ResponseEnum e, String msg, Object result) {
return code; super(e.getMsg());
} this.code = e.getCode();
this.result = result;
public void setCode(int code) {
this.code = code;
} }
} }

View File

@@ -2,6 +2,7 @@ package cn.celess.blog.service;
import cn.celess.blog.entity.PartnerSite; import cn.celess.blog.entity.PartnerSite;
import cn.celess.blog.entity.model.PageData; import cn.celess.blog.entity.model.PageData;
import cn.celess.blog.entity.request.LinkApplyReq;
import cn.celess.blog.entity.request.LinkReq; import cn.celess.blog.entity.request.LinkReq;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -53,4 +54,19 @@ public interface PartnerSiteService {
*/ */
List<PartnerSite> findAll(); List<PartnerSite> findAll();
/**
* 申请友链
*
* @param linkApplyReq linkApplyReq
* @return linkApplyReq
*/
PartnerSite apply(LinkApplyReq linkApplyReq);
/**
* 重写申请友链
*
* @param key key
* @return msg
*/
String reapply(String key);
} }

View File

@@ -3,18 +3,30 @@ package cn.celess.blog.service.serviceimpl;
import cn.celess.blog.enmu.ResponseEnum; import cn.celess.blog.enmu.ResponseEnum;
import cn.celess.blog.entity.PartnerSite; import cn.celess.blog.entity.PartnerSite;
import cn.celess.blog.entity.model.PageData; import cn.celess.blog.entity.model.PageData;
import cn.celess.blog.entity.request.LinkApplyReq;
import cn.celess.blog.entity.request.LinkReq; import cn.celess.blog.entity.request.LinkReq;
import cn.celess.blog.exception.MyException; import cn.celess.blog.exception.MyException;
import cn.celess.blog.mapper.PartnerMapper; import cn.celess.blog.mapper.PartnerMapper;
import cn.celess.blog.service.MailService;
import cn.celess.blog.service.PartnerSiteService; import cn.celess.blog.service.PartnerSiteService;
import cn.celess.blog.util.HttpUtil;
import cn.celess.blog.util.JwtUtil;
import cn.celess.blog.util.RedisUtil;
import cn.celess.blog.util.RegexUtil; import cn.celess.blog.util.RegexUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
import lombok.SneakyThrows;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/** /**
* @author : xiaohai * @author : xiaohai
@@ -24,6 +36,13 @@ import java.util.List;
public class PartnerSiteServiceImpl implements PartnerSiteService { public class PartnerSiteServiceImpl implements PartnerSiteService {
@Autowired @Autowired
PartnerMapper partnerMapper; PartnerMapper partnerMapper;
@Autowired
MailService mailService;
@Autowired
RedisUtil redisUtil;
private static final String SITE_NAME = "小海博客";
private static final String SITE_URL = "celess.cn";
private static final String SITE_EMAIL = "a@celess.cn";
@Override @Override
public PartnerSite create(LinkReq reqBody) { public PartnerSite create(LinkReq reqBody) {
@@ -86,10 +105,21 @@ public class PartnerSiteServiceImpl implements PartnerSiteService {
if (!reqBody.getUrl().contains("http://") && !reqBody.getUrl().contains("https://")) { if (!reqBody.getUrl().contains("http://") && !reqBody.getUrl().contains("https://")) {
reqBody.setUrl("http://" + reqBody.getUrl()); reqBody.setUrl("http://" + reqBody.getUrl());
} }
if (reqBody.isOpen() != partnerSite.getOpen() && !partnerSite.getNotification() && !StringUtils.isEmpty(partnerSite.getEmail())) {
SimpleMailMessage smm = new SimpleMailMessage();
smm.setTo(partnerSite.getEmail());
smm.setText("您的友链申请,已通过");
smm.setSubject("友链申请通过");
smm.setSentDate(new Date());
mailService.send(smm);
partnerSite.setNotification(true);
}
BeanUtils.copyProperties(reqBody, partnerSite);
partnerMapper.update(partnerSite);
partnerSite.setName(reqBody.getName()); partnerSite.setName(reqBody.getName());
partnerSite.setUrl(reqBody.getUrl()); partnerSite.setUrl(reqBody.getUrl());
partnerSite.setOpen(reqBody.isOpen()); partnerSite.setOpen(reqBody.isOpen());
partnerMapper.update(partnerSite);
return partnerSite; return partnerSite;
} }
@@ -108,5 +138,92 @@ public class PartnerSiteServiceImpl implements PartnerSiteService {
return all; return all;
} }
@SneakyThrows
@Override
public PartnerSite apply(LinkApplyReq linkApplyReq) {
// 空值字段
if (StringUtils.isEmpty(linkApplyReq.getName())
|| StringUtils.isEmpty(linkApplyReq.getUrl())
|| StringUtils.isEmpty(linkApplyReq.getEmail())
|| StringUtils.isEmpty(linkApplyReq.getLinkUrl())) {
throw new MyException(ResponseEnum.PARAMETERS_ERROR);
}
// 链接不合法
if (!RegexUtil.emailMatch(linkApplyReq.getEmail())) {
throw new MyException(ResponseEnum.PARAMETERS_EMAIL_ERROR);
}
if (!RegexUtil.urlMatch(linkApplyReq.getLinkUrl()) || !RegexUtil.urlMatch(linkApplyReq.getUrl())) {
throw new MyException(ResponseEnum.PARAMETERS_URL_ERROR);
}
if (!StringUtils.isEmpty(linkApplyReq.getIconPath()) && !RegexUtil.urlMatch(linkApplyReq.getIconPath())) {
throw new MyException(ResponseEnum.PARAMETERS_URL_ERROR);
}
// 非强制字段 设置空
if (StringUtils.isEmpty(linkApplyReq.getIconPath())) {
linkApplyReq.setIconPath("");
}
// 抓取页面
String resp = HttpUtil.getAfterRendering(linkApplyReq.getLinkUrl());
if (resp == null) {
throw new MyException(ResponseEnum.CANNOT_GET_DATA);
}
PartnerSite ps = new PartnerSite();
if (resp.contains(SITE_URL)) {
//包含站点
BeanUtils.copyProperties(linkApplyReq, ps);
ps.setNotification(false);
ps.setOpen(false);
boolean exists = partnerMapper.existsByUrl(linkApplyReq.getUrl());
if (!exists) {
partnerMapper.insert(ps);
} else {
ps.setId(partnerMapper.findByUrl(linkApplyReq.getUrl()).getId());
}
SimpleMailMessage smm = new SimpleMailMessage();
smm.setSubject("友链申请");
smm.setText("有一条友链申请" + (exists ? ",已存在的友链链接" : "") + "[\n" + linkApplyReq.toString() + "\n]");
smm.setTo(SITE_EMAIL);
smm.setSentDate(new Date());
mailService.send(smm);
} else {
// 不包含站点
String uuid;
ObjectMapper mapper = new ObjectMapper();
if (redisUtil.hasKey(linkApplyReq.getUrl())) {
uuid = redisUtil.get(linkApplyReq.getUrl());
if (!redisUtil.hasKey(uuid)) {
redisUtil.setEx(uuid, mapper.writeValueAsString(linkApplyReq), 10, TimeUnit.MINUTES);
}
} else {
uuid = UUID.randomUUID().toString().replaceAll("-", "");
redisUtil.setEx(uuid, mapper.writeValueAsString(linkApplyReq), 10, TimeUnit.MINUTES);
redisUtil.setEx(linkApplyReq.getUrl(), uuid, 10, TimeUnit.MINUTES);
}
throw new MyException(ResponseEnum.APPLY_LINK_NO_ADD_THIS_SITE, null, uuid);
}
return ps;
}
@SneakyThrows
@Override
public String reapply(String key) {
if (!redisUtil.hasKey(key)) {
throw new MyException(ResponseEnum.DATA_EXPIRED);
}
String s = redisUtil.get(key);
ObjectMapper mapper = new ObjectMapper();
LinkApplyReq linkApplyReq = mapper.readValue(s, LinkApplyReq.class);
if (linkApplyReq == null) {
throw new MyException(ResponseEnum.DATA_NOT_EXIST);
}
SimpleMailMessage smm = new SimpleMailMessage();
smm.setSubject("友链申请");
smm.setText("有一条未抓取到信息的友链申请,[\n" + linkApplyReq.toString() + "\n]");
smm.setTo(SITE_EMAIL);
smm.setSentDate(new Date());
mailService.send(smm);
redisUtil.delete(key);
redisUtil.delete(linkApplyReq.getUrl());
return "success";
}
} }

View File

@@ -88,19 +88,15 @@ public class WebUpdateInfoServiceImpl implements WebUpdateInfoService {
JSONObject jsonObject = new JSONObject(); JSONObject jsonObject = new JSONObject();
jsonObject.put("lastUpdateTime", DateFormatUtil.get(webUpdateInfoMapper.getLastestOne().getUpdateTime())); jsonObject.put("lastUpdateTime", DateFormatUtil.get(webUpdateInfoMapper.getLastestOne().getUpdateTime()));
jsonObject.put("lastUpdateInfo", webUpdateInfoMapper.getLastestOne().getUpdateInfo()); jsonObject.put("lastUpdateInfo", webUpdateInfoMapper.getLastestOne().getUpdateInfo());
try { JSONArray array = JSONArray.fromObject(HttpUtil.get("https://api.github.com/repos/xiaohai2271/blog-frontEnd/commits?page=1&per_page=1"));
JSONArray array = JSONArray.fromObject(HttpUtil.get("https://api.github.com/repos/xiaohai2271/blog-frontEnd/commits?page=1&per_page=1")); JSONObject object = array.getJSONObject(0);
JSONObject object = array.getJSONObject(0); JSONObject commit = object.getJSONObject("commit");
JSONObject commit = object.getJSONObject("commit"); jsonObject.put("lastCommit", commit.getString("message"));
jsonObject.put("lastCommit", commit.getString("message")); jsonObject.put("committerAuthor", commit.getJSONObject("committer").getString("name"));
jsonObject.put("committerAuthor", commit.getJSONObject("committer").getString("name")); SimpleDateFormat sdf = new SimpleDateFormat();
SimpleDateFormat sdf = new SimpleDateFormat(); Instant parse = Instant.parse(commit.getJSONObject("committer").getString("date"));
Instant parse = Instant.parse(commit.getJSONObject("committer").getString("date")); jsonObject.put("committerDate", DateFormatUtil.get(Date.from(parse)));
jsonObject.put("committerDate", DateFormatUtil.get(Date.from(parse))); jsonObject.put("commitUrl", "https://github.com/xiaohai2271/blog-frontEnd/tree/" + object.getString("sha"));
jsonObject.put("commitUrl", "https://github.com/xiaohai2271/blog-frontEnd/tree/" + object.getString("sha"));
} catch (IOException e) {
log.info("网络请求失败{}", e.getMessage());
}
return jsonObject; return jsonObject;
} }

View File

@@ -1,12 +1,16 @@
package cn.celess.blog.util; package cn.celess.blog.util;
import java.io.BufferedReader; import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.net.MalformedURLException;
import java.io.InputStreamReader; import java.util.Objects;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
/** /**
* @Author: 小海 * @Author: 小海
@@ -14,8 +18,10 @@ import java.nio.charset.StandardCharsets;
* @Desc: * @Desc:
*/ */
public class HttpUtil { public class HttpUtil {
private static final OkHttpClient CLIENT = new OkHttpClient();
public static String get(String urlStr) throws IOException {
/*public static String get(String urlStr) throws IOException {
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
@@ -48,4 +54,33 @@ public class HttpUtil {
} }
return sb.toString(); return sb.toString();
} }
*/
public static String get(String urlStr) {
Request request = new Request.Builder()
.url(urlStr)
.get()
.build();
try (Response response = CLIENT.newCall(request).execute()) {
return Objects.requireNonNull(response.body()).string();
} catch (IOException e) {
return null;
}
}
public static String getAfterRendering(String url) {
try (final WebClient webClient = new WebClient(BrowserVersion.CHROME)) {
webClient.getOptions().setCssEnabled(false);
webClient.getOptions().setJavaScriptEnabled(true);
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
webClient.getOptions().setThrowExceptionOnScriptError(false);
webClient.getOptions().setDownloadImages(false);
webClient.getOptions().setActiveXNative(false);
webClient.setAjaxController(new NicelyResynchronizingAjaxController());
final HtmlPage page = webClient.getPage(url);
return page.asXml();
} catch (IOException e) {
return null;
}
}
} }

View File

@@ -9,20 +9,26 @@
<result column="l_icon_path" property="iconPath"/> <result column="l_icon_path" property="iconPath"/>
<result column="l_desc" property="desc"/> <result column="l_desc" property="desc"/>
<result column="is_delete" property="delete"/> <result column="is_delete" property="delete"/>
<result column="l_email" property="email"/>
<result column="l_notification" property="notification"/>
</resultMap> </resultMap>
<insert id="insert" parameterType="cn.celess.blog.entity.PartnerSite" useGeneratedKeys="true" keyProperty="id"> <insert id="insert" parameterType="cn.celess.blog.entity.PartnerSite" useGeneratedKeys="true" keyProperty="id">
insert into links (l_name, l_is_open, l_url, l_icon_path, l_desc, is_delete) insert into links (l_name, l_is_open, l_url, l_icon_path, l_desc, l_email, l_notification, is_delete)
values (#{name}, #{open}, #{url}, #{iconPath}, #{desc}, false) values (#{name}, #{open}, #{url}, #{iconPath}, #{desc}, #{email}, #{notification}, false)
</insert> </insert>
<update id="update" parameterType="cn.celess.blog.entity.PartnerSite"> <update id="update" parameterType="cn.celess.blog.entity.PartnerSite">
update links set update links
<if test="open!=null">l_is_open=#{open},</if> <trim prefix="set" suffixOverrides=",">
<if test="iconPath!=null">l_icon_path=#{iconPath},</if> <if test="open!=null">l_is_open=#{open},</if>
<if test="desc!=null">l_desc=#{desc},</if> <if test="iconPath!=null">l_icon_path=#{iconPath},</if>
<if test="url!=null">l_url=#{url},</if> <if test="desc!=null">l_desc=#{desc},</if>
<if test="name!=null">l_name=#{name}</if> <if test="url!=null">l_url=#{url},</if>
<if test="name!=null">l_name=#{name},</if>
<if test="notification!=null">l_notification=#{notification},</if>
<if test="email!=null">l_email=#{email}</if>
</trim>
where l_id=#{id} where l_id=#{id}
</update> </update>

View File

@@ -4,27 +4,37 @@ package cn.celess.blog;
import cn.celess.blog.entity.Response; import cn.celess.blog.entity.Response;
import cn.celess.blog.entity.model.UserModel; import cn.celess.blog.entity.model.UserModel;
import cn.celess.blog.entity.request.LoginReq; import cn.celess.blog.entity.request.LoginReq;
import cn.celess.blog.service.MailService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static cn.celess.blog.enmu.ResponseEnum.SUCCESS; import static cn.celess.blog.enmu.ResponseEnum.SUCCESS;
@@ -44,16 +54,24 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@ActiveProfiles("test") @ActiveProfiles("test")
public class BaseTest { public class BaseTest {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
protected MockMvc mockMvc; protected MockMvc mockMvc;
protected final static String Code = "code"; protected final static String Code = "code";
protected final static String Result = "result"; protected final static String Result = "result";
private static String userToken = null;
private static String adminToken = null;
/** /**
* jackson 序列化/反序列化Json * jackson 序列化/反序列化Json
*/ */
protected final ObjectMapper mapper = new ObjectMapper(); protected final ObjectMapper mapper = new ObjectMapper();
protected static final TypeReference<?> BOOLEAN_TYPE = new TypeReference<Response<Boolean>>() {
};
protected static final TypeReference<?> STRING_TYPE = new TypeReference<Response<String>>() {
};
protected static final TypeReference<?> OBJECT_TYPE = new TypeReference<Response<Object>>() {
};
protected static final TypeReference<?> MAP_OBJECT_TYPE = new TypeReference<Response<Map<String, Object>>>() {
};
@Autowired @Autowired
private WebApplicationContext wac; private WebApplicationContext wac;
protected MockHttpSession session; protected MockHttpSession session;
@@ -76,14 +94,13 @@ public class BaseTest {
* @return token * @return token
*/ */
protected String adminLogin() { protected String adminLogin() {
if (adminToken != null) return adminToken;
LoginReq req = new LoginReq(); LoginReq req = new LoginReq();
req.setEmail("a@celess.cn"); req.setEmail("a@celess.cn");
req.setPassword("123456789"); req.setPassword("123456789");
req.setIsRememberMe(false); req.setIsRememberMe(true);
adminToken = login(req); String token = login(req);
assertNotNull(adminToken); assertNotNull(token);
return adminToken; return token;
} }
/** /**
@@ -92,14 +109,13 @@ public class BaseTest {
* @return token * @return token
*/ */
protected String userLogin() { protected String userLogin() {
if (userToken != null) return userToken;
LoginReq req = new LoginReq(); LoginReq req = new LoginReq();
req.setEmail("zh56462271@qq.com"); req.setEmail("zh56462271@qq.com");
req.setPassword("123456789"); req.setPassword("123456789");
req.setIsRememberMe(false); req.setIsRememberMe(true);
userToken = login(req); String token = login(req);
assertNotNull(userToken); assertNotNull(token);
return userToken; return token;
} }
/** /**
@@ -120,8 +136,10 @@ public class BaseTest {
assertNotNull(token); assertNotNull(token);
return token; return token;
} catch (Exception e) { } catch (Exception e) {
logger.error("测试登录错误");
e.printStackTrace(); e.printStackTrace();
} }
assertNotNull(str);
return null; return null;
} }
@@ -130,9 +148,11 @@ public class BaseTest {
// 测试登录 // 测试登录
assertNotNull(userLogin()); assertNotNull(userLogin());
assertNotNull(adminLogin()); assertNotNull(adminLogin());
assertNotEquals(userLogin(), adminLogin());
try { try {
// 测试getMockData方法 // 测试getMockData方法
assertNotNull(getMockData(get("/headerInfo"))); assertNotNull(getMockData(get("/headerInfo")));
getMockData((get("/headerInfo"))).andDo(result -> assertNotNull(getResponse(result, OBJECT_TYPE)));
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -141,13 +161,21 @@ public class BaseTest {
/** /**
* 产生指定长度的随机字符 * 产生指定长度的随机字符
* *
* @param len * @param len len
* @return * @return str
*/ */
protected String randomStr(int len) { protected String randomStr(int len) {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, len); return UUID.randomUUID().toString().replaceAll("-", "").substring(0, len);
} }
/**
* 产生指定长度的随机字符
*
* @return str
*/
protected String randomStr() {
return UUID.randomUUID().toString();
}
/** /**
* 抽离的mock请求方法 * 抽离的mock请求方法
@@ -191,4 +219,101 @@ public class BaseTest {
} }
return mockMvc.perform(builder).andExpect(status().isOk()); return mockMvc.perform(builder).andExpect(status().isOk());
} }
protected <T> Response<T> getResponse(String json) {
return getResponse(json, OBJECT_TYPE);
}
protected <T> Response<T> getResponse(MvcResult result) {
return getResponse(result, OBJECT_TYPE);
}
/**
* 根据json 信息反序列化成Response对象
*
* @param json json
* @param <T> 泛型
* @return Response对象
*/
protected <T> Response<T> getResponse(String json, TypeReference<?> responseType) {
Response<T> response = null;
System.out.println(responseType.getType());
try {
response = mapper.readValue(json, responseType);
} catch (IOException e) {
logger.error("解析json Response对象错误json:[{}]", json);
e.printStackTrace();
}
assertNotNull(response);
return response;
}
/**
* 根据json 信息反序列化成Response对象
*
* @param result MvcResult
* @param <T> 泛型
* @return Response对象
*/
protected <T> Response<T> getResponse(MvcResult result, TypeReference<?> responseType) {
try {
return getResponse(result.getResponse().getContentAsString(), responseType);
} catch (UnsupportedEncodingException e) {
logger.error("解析json Response对象错误result:[{}]", result);
e.printStackTrace();
}
return null;
}
/**
* 修改 mailService 的实现类
*
* @param service service 类
* @param mailFiledName service 中自动注入的mailService字段名
*/
protected void mockEmailServiceInstance(Object service, String mailFiledName) {
Field mailServiceField;
try {
mailServiceField = service.getClass().getDeclaredField(mailFiledName);
mailServiceField.setAccessible(true);
mailServiceField.set(service, new TestMailServiceImpl());
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
@Slf4j
protected static class TestMailServiceImpl implements MailService {
@Override
public Boolean AsyncSend(SimpleMailMessage message) {
log.debug("异步邮件请求,SimpleMailMessage:[{}]", getJson(message));
return true;
}
@Override
public Boolean send(SimpleMailMessage message) {
log.debug("邮件请求,SimpleMailMessage:[{}]", getJson(message));
return true;
}
/**
* 转json
*
* @param o
* @return
*/
private String getJson(Object o) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(o);
} catch (JsonProcessingException e) {
return null;
}
}
}
} }

View File

@@ -3,31 +3,43 @@ package cn.celess.blog.controller;
import cn.celess.blog.BaseTest; import cn.celess.blog.BaseTest;
import cn.celess.blog.entity.PartnerSite; import cn.celess.blog.entity.PartnerSite;
import cn.celess.blog.entity.model.PageData; import cn.celess.blog.entity.model.PageData;
import cn.celess.blog.entity.request.LinkApplyReq;
import cn.celess.blog.entity.request.LinkReq; import cn.celess.blog.entity.request.LinkReq;
import cn.celess.blog.exception.MyException;
import cn.celess.blog.mapper.PartnerMapper; import cn.celess.blog.mapper.PartnerMapper;
import com.github.pagehelper.PageInfo; import cn.celess.blog.service.MailService;
import cn.celess.blog.service.PartnerSiteService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject; import net.sf.json.JSONObject;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.mail.SimpleMailMessage;
import java.lang.reflect.Field;
import java.util.UUID; import java.util.UUID;
import static cn.celess.blog.enmu.ResponseEnum.*; import static cn.celess.blog.enmu.ResponseEnum.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
@Slf4j
public class LinksControllerTest extends BaseTest { public class LinksControllerTest extends BaseTest {
@Autowired @Autowired
PartnerMapper mapper; PartnerMapper mapper;
@Autowired
PartnerSiteService partnerSiteService;
@Test @Test
public void create() throws Exception { public void create() throws Exception {
LinkReq linkReq = new LinkReq(); LinkReq linkReq = new LinkReq();
linkReq.setName(UUID.randomUUID().toString().substring(0, 4)); linkReq.setName(UUID.randomUUID().toString().substring(0, 4));
linkReq.setOpen(false); linkReq.setOpen(false);
linkReq.setUrl("https://example.com"); linkReq.setUrl("https://" + randomStr(4) + "celess.cn");
String token = adminLogin(); String token = adminLogin();
mockMvc.perform( mockMvc.perform(
post("/admin/links/create") post("/admin/links/create")
@@ -47,7 +59,8 @@ public class LinksControllerTest extends BaseTest {
// https/http // https/http
linkReq.setName(UUID.randomUUID().toString().substring(0, 4)); linkReq.setName(UUID.randomUUID().toString().substring(0, 4));
linkReq.setOpen(false); linkReq.setOpen(false);
linkReq.setUrl("example.com"); String url = randomStr(4) + ".celess.cn";
linkReq.setUrl(url);
mockMvc.perform( mockMvc.perform(
post("/admin/links/create") post("/admin/links/create")
.content(JSONObject.fromObject(linkReq).toString()) .content(JSONObject.fromObject(linkReq).toString())
@@ -57,7 +70,7 @@ public class LinksControllerTest extends BaseTest {
JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString()); JSONObject object = JSONObject.fromObject(result.getResponse().getContentAsString());
assertEquals(SUCCESS.getCode(), object.getInt(Code)); assertEquals(SUCCESS.getCode(), object.getInt(Code));
PartnerSite site = (PartnerSite) JSONObject.toBean(object.getJSONObject(Result), PartnerSite.class); PartnerSite site = (PartnerSite) JSONObject.toBean(object.getJSONObject(Result), PartnerSite.class);
assertEquals("http://example.com", site.getUrl()); assertEquals("http://" + url, site.getUrl());
}); });
// 测试已存在的数据 // 测试已存在的数据
@@ -76,7 +89,7 @@ public class LinksControllerTest extends BaseTest {
partnerSite.setOpen(true); partnerSite.setOpen(true);
partnerSite.setDesc(""); partnerSite.setDesc("");
partnerSite.setIconPath(""); partnerSite.setIconPath("");
partnerSite.setUrl("https://www.celess.cn"); partnerSite.setUrl("https://" + randomStr(4) + ".celess.cn");
mapper.insert(partnerSite); mapper.insert(partnerSite);
PartnerSite lastest = mapper.getLastest(); PartnerSite lastest = mapper.getLastest();
assertNotNull(lastest.getId()); assertNotNull(lastest.getId());
@@ -105,7 +118,7 @@ public class LinksControllerTest extends BaseTest {
partnerSite.setDesc(""); partnerSite.setDesc("");
partnerSite.setIconPath(""); partnerSite.setIconPath("");
partnerSite.setDelete(false); partnerSite.setDelete(false);
partnerSite.setUrl("https://www.celess.cn"); partnerSite.setUrl("https://" + randomStr(4) + ".celess.cn");
mapper.insert(partnerSite); mapper.insert(partnerSite);
// 查数据 // 查数据
PartnerSite lastest = mapper.getLastest(); PartnerSite lastest = mapper.getLastest();
@@ -166,18 +179,83 @@ public class LinksControllerTest extends BaseTest {
} }
// 手动测试 // 手动测试
// @Test @Test
public void apply() throws Exception { public void apply() {
long l = System.currentTimeMillis(); // 做service 层的测试
String url = "https://www.example.com"; mockEmailServiceInstance(partnerSiteService, "mailService");
mockMvc.perform(post("/apply?name=小海博客Api测试请忽略&url=" + url)).andDo(result -> { LinkApplyReq req = new LinkApplyReq();
assertEquals(SUCCESS.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); req.setName(randomStr(4));
}); req.setUrl("https://" + randomStr(4) + ".celess.cn");
System.out.println("耗时:" + (System.currentTimeMillis() - l) / 1000 + "s"); req.setIconPath("https://www.celess.cn/example.png");
url = "xxxxxxxxxm"; req.setDesc("desc :" + randomStr());
mockMvc.perform(post("/apply?name=小海博客Api测试请忽略&url=" + url)).andDo(result -> { req.setEmail(randomStr(4) + "@celess.cn");
assertEquals(PARAMETERS_URL_ERROR.getCode(), JSONObject.fromObject(result.getResponse().getContentAsString()).getInt(Code)); req.setLinkUrl(req.getUrl() + "/links");
}); try {
// 抓取不到数据的链接
partnerSiteService.apply(req);
} catch (MyException e) {
log.debug("测试抓取不到数据");
assertEquals(CANNOT_GET_DATA.getCode(), e.getCode());
}
req.setLinkUrl("https://bing.com");
req.setUrl(req.getLinkUrl());
try {
partnerSiteService.apply(req);
} catch (MyException e) {
log.debug("测试未添加本站链接的友链申请");
assertEquals(APPLY_LINK_NO_ADD_THIS_SITE.getCode(), e.getCode());
assertNotNull(e.getResult());
try {
// 测试uuid一致性
log.debug("测试uuid一致性");
partnerSiteService.apply(req);
} catch (MyException e2) {
assertEquals(e.getResult(), e2.getResult());
}
}
log.debug("测试正常申请");
req.setLinkUrl("https://www.celess.cn");
req.setUrl(req.getLinkUrl());
PartnerSite apply = partnerSiteService.apply(req);
assertNotNull(apply);
assertNotNull(apply.getId());
}
@Test
public void reapply() {
mockEmailServiceInstance(partnerSiteService, "mailService");
try {
partnerSiteService.reapply(randomStr());
throw new AssertionError();
} catch (MyException e) {
assertEquals(DATA_EXPIRED.getCode(), e.getCode());
}
LinkApplyReq req = new LinkApplyReq();
req.setName(randomStr(4));
req.setIconPath("https://www.celess.cn/example.png");
req.setDesc("desc :" + randomStr());
req.setEmail(randomStr(4) + "@celess.cn");
req.setLinkUrl("https://bing.com");
req.setUrl(req.getLinkUrl());
String uuid = null;
try {
partnerSiteService.apply(req);
// err here
throw new AssertionError();
} catch (MyException e) {
uuid = (String) e.getResult();
String reapply = partnerSiteService.reapply(uuid);
assertEquals(reapply, "success");
}
try {
partnerSiteService.reapply(uuid);
throw new AssertionError();
} catch (MyException e) {
assertEquals(DATA_EXPIRED.getCode(), e.getCode());
}
} }
} }

View File

@@ -35,7 +35,7 @@ public class PartnerMapperTest extends BaseTest {
partnerSite.setIconPath(randomStr(5)); partnerSite.setIconPath(randomStr(5));
partnerSite.setDesc(randomStr(5)); partnerSite.setDesc(randomStr(5));
partnerSite.setOpen(false); partnerSite.setOpen(false);
partnerSite.setUrl("www.celess.cn?random=" + randomStr(4)); partnerSite.setUrl("www.celess.cn/?random=" + randomStr(4));
assertEquals(1, partnerMapper.update(partnerSite)); assertEquals(1, partnerMapper.update(partnerSite));
} }

View File

@@ -0,0 +1,22 @@
package cn.celess.blog.util;
import cn.celess.blog.BaseTest;
import cn.celess.blog.enmu.ResponseEnum;
import cn.celess.blog.entity.Response;
import org.junit.Test;
import java.util.Map;
import static org.junit.Assert.*;
public class HttpUtilTest extends BaseTest {
@Test
public void get() {
String s = HttpUtil.get("https://api.celess.cn/headerInfo");
assertNotNull(s);
Response<Map<String, Object>> response = getResponse(s, MAP_OBJECT_TYPE);
assertEquals(ResponseEnum.SUCCESS.getCode(), response.getCode());
assertNotEquals(0, response.getResult().size());
}
}