Repository: nosqlcoco/springboot-weapp-demo Branch: master Commit: c843e2b69204 Files: 44 Total size: 69.4 KB Directory structure: gitextract_c7s5u6lo/ ├── README.md ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── weapp/ │ │ ├── Application.java │ │ ├── SwaggerConfig.java │ │ ├── aop/ │ │ │ └── ApiAspect.java │ │ ├── common/ │ │ │ ├── aes/ │ │ │ │ └── AES.java │ │ │ ├── annotation/ │ │ │ │ └── Api.java │ │ │ ├── constant/ │ │ │ │ └── ApiConstant.java │ │ │ ├── properties/ │ │ │ │ └── WxAuth.java │ │ │ └── util/ │ │ │ ├── HttpRequest.java │ │ │ └── MongoPageable.java │ │ ├── controller/ │ │ │ ├── AppUserController.java │ │ │ ├── BaseController.java │ │ │ ├── UploadController.java │ │ │ ├── WxAuthController.java │ │ │ └── WxClubController.java │ │ ├── entity/ │ │ │ ├── app/ │ │ │ │ └── TUser.java │ │ │ ├── auth/ │ │ │ │ ├── AccessLog.java │ │ │ │ ├── ApiInfo.java │ │ │ │ └── AppKey.java │ │ │ └── wxclub/ │ │ │ └── Article.java │ │ ├── interceptor/ │ │ │ ├── ApiInterceptor.java │ │ │ └── ApiWebConfigure.java │ │ ├── listener/ │ │ │ └── ApiServletContextListener.java │ │ ├── redis/ │ │ │ ├── MyRedisCacheConfig.java │ │ │ └── RedisUtil.java │ │ ├── repository/ │ │ │ ├── AccessLogRepository.java │ │ │ ├── ApiInfoRepository.java │ │ │ ├── AppKeyRepository.java │ │ │ └── WxClubRepository.java │ │ ├── service/ │ │ │ ├── AccessLogService.java │ │ │ ├── ApiInfoService.java │ │ │ ├── AppKeyService.java │ │ │ ├── WxClubService.java │ │ │ └── WxService.java │ │ ├── webconfig/ │ │ │ └── WebConfigurer.java │ │ └── websocket/ │ │ ├── ChatWebSocketHandler.java │ │ ├── WebSocketConfig.java │ │ └── WebSocketHandshakeInterceptor.java │ └── resources/ │ ├── application.properties │ ├── error_code.properties │ └── log4j.properties └── test/ ├── java/ │ └── com/ │ └── weapp/ │ └── ApplicationTest.java └── resources/ └── application-test.properties ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # springboot-weapp-demo 微信小程序服务端接口,支持普通Http请求、上传文件、长连接,微信登录及敏感数据解密。后台服务使用springboot框架搭建,mongodb做数据库,redis做缓存。 运行环境:JDK8+ 注意:如果你是本地运行,需要修改为你本地对应的主机和端口。 长连接需使用ws协议 ####更新日志: - 2016-12-18 - 拦截器记录口访问日志存储mongodb - 2016-11-24 - 小程序code换取session_key和openid - 小程序登录用户敏感数据解密 - 2016-11-22 - 配置Https - 2016-11-18 - 重写小程序http测试和上传文件接口 - 统一接口返回返回状态码和格式 - 2016-11-20 - 添加Redis缓存 - 添加微信登录状态维护和用户数据解密接口 #### 一、测试小程序wx.request接口 ```javascript wx.request({ url: 'http://localhost:9090/weappservice/api/v1/user/get/{id}', data: {appId: 'JWEJIJ345QHWJKENVKF', apiName: 'GET_USER'}, method: 'GET', //return JSON format,like: {"id":"{id}"} success: function(res){ console.log(res.data); }, fail: function(res){ }, complete: function(res){ } }); ``` #### 二、测试小程序wx.uploadFile接口,单张上传 ```javascript wx.uploadFile({ url: 'http://localhost:9090/weappservice/api/v1/upload/image', //文件临时路径 filePath: tempFilePath, name: 'file', header: {}, formData: {appId: 'JWEJIJ345QHWJKENVKF', apiName: 'UPLOAD_IMAGE'}, success: function(res){ console.log(res.data) }, fail: function(res){ }, complete: function(res){ } }); ```
状态码(errcode) 说明(msg)
0 图片路径
40010 请选择上传文件!
40011 文件上传失败
#### 三、测试小程序websocket相关接口 ```javascript //发起websocket连接 wx.connectSocket({ url: 'ws://localhost:9090/weappservice/websocket?name=xiaoqiang', //这里写了参数,但是参数没有发送出去,大家可以试试,已经邮件反馈微信团队了,等待回复。所以把参数拼接在url后面。 data: { 'name1': 'xiaoqiang1' } }), //监听打开事件 wx.onSocketOpen(function(res) { console.log('WebSocket连接已打开!'); }), //接收消息,接收的消息是json字符串,需要JSON.parse转成JSON对象 wx.onSocketMessage(function(res){ var data = JSON.parse(res.data); console.log(data); }), //发送消息,消息对象属性key(user和content)不能自定义。 wx.sendSocketMessage({ data: JSON.stringify({ user: 'xiaoqaing', content: 'Hi, My name is xiaoqiang' }), success: function(res){ console.log('消息发送成功!') } }) ``` 我的微信小程序DEMO:[weixin_smallexe](https://github.com/cocoli/weixin_smallexe) PS:我的公众号: ![](https://github.com/cocoli/weixin_smallexe/blob/master/screenshot/dingyuhao.JPG?raw=true) ================================================ FILE: pom.xml ================================================ 4.0.0 com.wxapp wxapp 1.0.0 jar wxchatdemo1.0.0 wechat app API Project org.springframework.boot spring-boot-starter-parent 1.4.2.RELEASE UTF-8 1.8 1.54 2.2.2 1.8.3 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-websocket org.springframework.boot spring-boot-starter-data-mongodb org.springframework.boot spring-boot-starter-redis org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-web com.alibaba fastjson 1.2.31 org.projectlombok lombok com.google.guava guava 19.0 commons-logging commons-logging 1.2 org.apache.commons commons-lang3 3.4 org.apache.httpcomponents httpclient org.apache.httpcomponents httpcore commons-codec commons-codec org.bouncycastle bcprov-jdk15on ${bcprov.version} io.springfox springfox-swagger2 ${swagger2.version} io.springfox springfox-swagger-ui ${swagger2.version} org.jsoup jsoup ${jsoup.version} org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-maven-plugin ================================================ FILE: src/main/java/com/weapp/Application.java ================================================ package com.weapp; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Properties; import org.apache.catalina.connector.Connector; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.util.SocketUtils; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.weapp.common.constant.ApiConstant; import com.weapp.common.properties.WxAuth; import com.weapp.entity.auth.AppKey; import com.weapp.repository.AppKeyRepository; /** * * @author Shanqiang Ke * @version 1.0.0 * @blog http://nosqlcoco.cnblogs.com * @since 2016-10-15 */ @SpringBootApplication @ComponentScan(value = "com.weapp") @EnableConfigurationProperties(value={WxAuth.class}) public class Application implements CommandLineRunner{ @Autowired private AppKeyRepository repository; private static ImmutableMaperrorCodeMap = null; static { try { Properties prop = PropertiesLoaderUtils.loadAllProperties("error_code.properties"); errorCodeMap = Maps.fromProperties(prop); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Override public void run(String... arg0) throws Exception { repository.deleteAll(); String[] apiNames = new String[]{ApiConstant.GET_USER,ApiConstant.POST_USER,ApiConstant.PUT_USER, ApiConstant.DELETE_USER,ApiConstant.WX_CODE,ApiConstant.WX_DECODE_USERINFO, ApiConstant.WX_CLUB_ARTICLES,ApiConstant.WX_CLUB_SEARCH}; Map> apiMap = Maps.newHashMap(); for(String apiName : apiNames){ MaptmpMap = new HashMap(); tmpMap.put("calltimes", 0); tmpMap.put("alltimes", 10000); apiMap.put(apiName, tmpMap); } repository.save(new AppKey("JWEJIJ345QHWJKENVKF", "sdsd", new Date(), new Date(), "1", false, apiMap)); } @Bean public ImmutableMap errorCodeMap(){ return errorCodeMap; } @Bean public EmbeddedServletContainerFactory servletContainer() { TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory(); tomcat.addAdditionalTomcatConnectors(createStandardConnector()); return tomcat; } @Bean public Integer port() { return SocketUtils.findAvailableTcpPort(); } private Connector createStandardConnector() { Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); connector.setScheme("http"); connector.setPort(port()); return connector; } } ================================================ FILE: src/main/java/com/weapp/SwaggerConfig.java ================================================ package com.weapp; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class SwaggerConfig { @Value("${server.context-path}") private String pathMapping; @Bean public Docket createRestApi() { System.out.println("http://localhost:8080" + pathMapping + "/swagger-ui.html"); return new Docket(DocumentationType.SWAGGER_2) .groupName("test") .genericModelSubstitutes(ResponseEntity.class) .useDefaultResponseMessages(true) .forCodeGeneration(false) .pathMapping(pathMapping) .select() .apis(RequestHandlerSelectors.basePackage("com.weapp.controller")) .paths(PathSelectors.any()) .build(); //.apiInfo(apiInfo()); } // private ApiInfo apiInfo() { // return new ApiInfoBuilder() // .title("微信小程序后台服务API") // .description("更多小程序知识,请关注微信公众号『柯善强的随思笔记』") // .termsOfServiceUrl("http://www.cnblogs.com/nosqlcoco/") // .contact("柯善强") // .version("1.0").build(); // } } ================================================ FILE: src/main/java/com/weapp/aop/ApiAspect.java ================================================ package com.weapp.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; @Aspect @Component public class ApiAspect { @Pointcut(value="@annotation(com.weapp.common.annotation.Api)") public void apiAspect(){ } @Around("apiAspect()") public Object doAround(ProceedingJoinPoint pjp) throws Exception{ Object result = null; StopWatch sw = new StopWatch(getClass().getSimpleName()); try { sw.start(pjp.getSignature().getName()); result = pjp.proceed(); } catch(Throwable e){ e.printStackTrace(); }finally { sw.stop(); } return result; } } ================================================ FILE: src/main/java/com/weapp/common/aes/AES.java ================================================ package com.weapp.common.aes; import java.io.UnsupportedEncodingException; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.bouncycastle.jce.provider.BouncyCastleProvider; public class AES { public static boolean initialized = false; /** * AES解密 * @param content 密文 * @return * @throws InvalidAlgorithmParameterException * @throws NoSuchProviderException */ public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException { initialize(); try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); Key sKeySpec = new SecretKeySpec(keyByte, "AES"); cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化 byte[] result = cipher.doFinal(content); return result; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public static void initialize(){ if (initialized) return; Security.addProvider(new BouncyCastleProvider()); initialized = true; } //生成iv public static AlgorithmParameters generateIV(byte[] iv) throws Exception{ AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); params.init(new IvParameterSpec(iv)); return params; } } ================================================ FILE: src/main/java/com/weapp/common/annotation/Api.java ================================================ package com.weapp.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * API注解,使用在Controller控制器方法上 * @author xiaoqiang * */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Api { /*接口名称*/ String name(); /*每天上限*/ int accessLimit() default 10000; /*接口版本*/ String version() default "v1"; /*接口禁用*/ boolean disabled() default false; /*参数解密算法*/ String algorithm() default "none"; } ================================================ FILE: src/main/java/com/weapp/common/constant/ApiConstant.java ================================================ package com.weapp.common.constant; /** * API常量 * @author xiaoqiang * */ public class ApiConstant { /*测试*/ public final static String TEST_HTTP = "TEST_HTTP"; /*获取用户信息*/ public final static String GET_USER = "GET_USER"; /*创建用户信息*/ public final static String POST_USER = "POST_USER"; /*更新用户信息*/ public final static String PUT_USER = "PUT_USER"; /*删除用户信息*/ public final static String DELETE_USER = "DELETE_USER"; /*上传图片*/ public final static String UPLOAD_IMAGE = "UPLOAD_IMAGE"; /*微信code*/ public static final String WX_CODE = "WX_CODE"; /*校验微信用户信息完整性*/ public static final String WX_CHECK_USER = "WX_CHECK_USER"; /*解密用户信息*/ public static final String WX_DECODE_USERINFO = "WX_DECODE_USERINFO"; /* club社区专栏 */ public static final String WX_CLUB_ARTICLES = "WX_CLUB_ARTICLES"; /* club社区专栏文章搜索 */ public static final String WX_CLUB_SEARCH = "WX_CLUB_SEARCH"; } ================================================ FILE: src/main/java/com/weapp/common/properties/WxAuth.java ================================================ package com.weapp.common.properties; import org.springframework.boot.context.properties.ConfigurationProperties; import lombok.Data; @ConfigurationProperties(prefix = "wxapp") @Data public class WxAuth { private String appId; private String secret; private String grantType; private String sessionHost; } ================================================ FILE: src/main/java/com/weapp/common/util/HttpRequest.java ================================================ /** * * @project ApiService * @filename HttpRequest.java * @date 2015年8月16日 * @author KeShanqiang * */ package com.weapp.common.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.URL; import java.net.URLConnection; public class HttpRequest { /** * 向指定URL发送GET方法的请求 * * @param url * 发送请求的URL * @param param * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return URL 所代表远程资源的响应结果 */ public static String sendGet(String url, String param) { String result = ""; BufferedReader in = null; try { String urlNameString = url + "?" + param; URL realUrl = new URL(urlNameString); // 打开和URL之间的连接 URLConnection connection = realUrl.openConnection(); // 设置通用的请求属性 connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); // 建立实际的连接 connection.connect(); // 定义 BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader( connection.getInputStream())); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { e.printStackTrace(); } // 使用finally块来关闭输入流 finally { try { if (in != null) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return result; } /** * 向指定 URL 发送POST方法的请求 * * @param url * 发送请求的 URL * @param param * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return 所代表远程资源的响应结果 */ public static String sendPost(String url, String param) { PrintWriter out = null; BufferedReader in = null; String result = ""; try { URL realUrl = new URL(url); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("authorization", "Authorization: Basic token=0abf1040cda747f1bd724719fd2c8496"); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // 获取URLConnection对象对应的输出流 out = new PrintWriter(conn.getOutputStream()); out.print(param); // flush输出流的缓冲 out.flush(); // 定义BufferedReader输入流来读取URL的响应 in = new BufferedReader( new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { e.printStackTrace(); } //使用finally块来关闭输出流、输入流 finally{ try{ if(out!=null){ out.close(); } if(in!=null){ in.close(); } } catch(IOException ex){ ex.printStackTrace(); } } return result; } } ================================================ FILE: src/main/java/com/weapp/common/util/MongoPageable.java ================================================ package com.weapp.common.util; import java.io.Serializable; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; public class MongoPageable implements Serializable, Pageable{ /** * mongodb分页 */ private static final long serialVersionUID = 1L; // 当前页 private Integer pagenumber = 1; // 当前页面条数 private Integer pagesize = 10; @Override public int getPageNumber() { return 0; } @Override public int getPageSize() { return 0; } @Override public int getOffset() { return 0; } @Override public Pageable next() { return null; } @Override public Pageable previousOrFirst() { return null; } @Override public Pageable first() { return null; } @Override public boolean hasPrevious() { return false; } public Integer getPagenumber() { return pagenumber; } public void setPagenumber(Integer pagenumber) { this.pagenumber = pagenumber; } public Integer getPagesize() { return pagesize; } public void setPagesize(Integer pagesize) { this.pagesize = pagesize; } @Override public Sort getSort() { return null; } } ================================================ FILE: src/main/java/com/weapp/controller/AppUserController.java ================================================ package com.weapp.controller; import java.util.Map; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.google.common.collect.ImmutableMap; import com.weapp.common.annotation.Api; import com.weapp.common.constant.ApiConstant; @RestController @RequestMapping public class AppUserController { @Api(name=ApiConstant.GET_USER) @RequestMapping(value = "/api/v1/user/{id}", method = RequestMethod.GET, produces = "application/json") public Map get(@PathVariable String id){ ImmutableMap map = ImmutableMap.of("id", id); return map; } @Api(name=ApiConstant.POST_USER) @RequestMapping(value = "/api/v1/user/{id}", method = RequestMethod.POST, produces = "application/json") public Map post(@PathVariable String id){ ImmutableMap map = ImmutableMap.of("id", id); return map; } @Api(name=ApiConstant.PUT_USER) @RequestMapping(value = "/api/v1/user/{id}", method = RequestMethod.PUT, produces = "application/json") public Map put(@PathVariable String id){ ImmutableMap map = ImmutableMap.of("id", id); return map; } @Api(name=ApiConstant.DELETE_USER) @RequestMapping(value = "/api/v1/user/{id}", method = RequestMethod.DELETE, produces = "application/json") public Map delete(@PathVariable String id){ ImmutableMap map = ImmutableMap.of("id", id); return map; } } ================================================ FILE: src/main/java/com/weapp/controller/BaseController.java ================================================ package com.weapp.controller; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.google.common.collect.ImmutableMap; @Component public abstract class BaseController { @Autowired private ImmutableMap errorCodeMap; /** * 接口数据返回 * @param errorCode * @param data * @return */ protected Map rtnParam(Integer errorCode,Object data) { //正常的业务逻辑 if(errorCode == 0){ return ImmutableMap.of("errorCode", errorCode,"data", (data == null)? new Object() : data); }else{ return ImmutableMap.of("errorCode", errorCode, "msg", errorCodeMap.get(String.valueOf(errorCode))); } } } ================================================ FILE: src/main/java/com/weapp/controller/UploadController.java ================================================ package com.weapp.controller; import java.io.File; import java.io.IOException; import java.util.Map; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.FileCopyUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import com.google.common.collect.ImmutableMap; import com.weapp.common.annotation.Api; import com.weapp.common.constant.ApiConstant; @RestController public class UploadController extends BaseController{ //文件存储路径 @Value("${img.local.path}") private String imgLocalPath; //文件网络访问路径 @Value("${img.host}") private String imgHost; /** * 上传文件 * @param file * @return */ @Api(name = ApiConstant.UPLOAD_IMAGE) @RequestMapping(value = "/api/v1/upload/image", method = RequestMethod.POST, produces = "application/json") public Map uploadImage(@RequestParam(required=true,value="file")MultipartFile file){ if(null == file){ return rtnParam(40010, null); } String random = RandomStringUtils.randomAlphabetic(16); String fileName = random + ".jpg"; try { String uploadDirName = imgLocalPath.substring(imgLocalPath.lastIndexOf("/"), imgLocalPath.length()); FileCopyUtils.copy(file.getBytes(), new File(imgLocalPath + "/", fileName)); return rtnParam(0, ImmutableMap.of("url", imgHost + uploadDirName + "/" + fileName)); } catch (IOException e) { e.printStackTrace(); } return rtnParam(40011, null); } } ================================================ FILE: src/main/java/com/weapp/controller/WxAuthController.java ================================================ package com.weapp.controller; import java.io.UnsupportedEncodingException; import java.security.InvalidAlgorithmParameterException; import java.util.Arrays; import java.util.Map; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.DigestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.google.common.collect.ImmutableMap; import com.weapp.common.aes.AES; import com.weapp.common.annotation.Api; import com.weapp.common.constant.ApiConstant; import com.weapp.redis.RedisUtil; import com.weapp.service.WxService; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; /** * 微信用户认证相关 * @author xiaoqiang * */ @RestController public class WxAuthController extends BaseController{ @Autowired private WxService wxService; @Autowired private RedisUtil redisUtil; /** * 根据客户端传过来的code从微信服务器获取appid和session_key,然后生成3rdkey返回给客户端,后续请求客户端传3rdkey来维护客户端登录态 * @param wxCode 小程序登录时获取的code * @return */ @ApiOperation(value = "获取sessionId", notes = "小用户允许登录后,使用code 换取 session_key api,将 code 换成 openid 和 session_key") @ApiImplicitParam(name = "code", value = "用户登录回调内容会带上 ", required = true, dataType = "String") @Api(name = ApiConstant.WX_CODE) @RequestMapping(value = "/api/v1/wx/getSession", method = RequestMethod.GET, produces = "application/json") public Map createSssion(@RequestParam(required = true,value = "code")String wxCode){ Map wxSessionMap = wxService.getWxSession(wxCode); if(null == wxSessionMap){ return rtnParam(50010, null); } //获取异常 if(wxSessionMap.containsKey("errcode")){ return rtnParam(50020, null); } String wxOpenId = (String)wxSessionMap.get("openid"); String wxSessionKey = (String)wxSessionMap.get("session_key"); System.out.println(wxSessionKey); Long expires = Long.valueOf(String.valueOf(wxSessionMap.get("expires_in"))); String thirdSession = wxService.create3rdSession(wxOpenId, wxSessionKey, expires); return rtnParam(0, ImmutableMap.of("sessionId",thirdSession)); } /** * 验证用户信息完整性 * @param rawData 微信用户基本信息 * @param signature 数据签名 * @param sessionId 会话ID * @return */ @Api(name = ApiConstant.WX_CHECK_USER) @RequestMapping(value = "/api/v1/wx/checkUserInfo", method = RequestMethod.GET, produces = "application/json") public Map checkUserInfo(@RequestParam(required = true,value = "rawData")String rawData, @RequestParam(required = true,value = "signature")String signature, @RequestParam(required = true,defaultValue = "sessionId")String sessionId){ Object wxSessionObj = redisUtil.get(sessionId); if(null == wxSessionObj){ return rtnParam(40008, null); } String wxSessionStr = (String)wxSessionObj; String sessionKey = wxSessionStr.split("#")[0]; StringBuffer sb = new StringBuffer(rawData); sb.append(sessionKey); byte[] encryData = DigestUtils.sha1(sb.toString()); byte[] signatureData = signature.getBytes(); Boolean checkStatus = Arrays.equals(encryData, signatureData); return rtnParam(0, ImmutableMap.of("checkPass", checkStatus)); } /** * 获取用户openId和unionId数据(如果没绑定微信开放平台,解密数据中不包含unionId) * @param encryptedData 加密数据 * @param iv 加密算法的初始向量 * @param sessionId 会话ID * @return */ @Api(name = ApiConstant.WX_DECODE_USERINFO) @RequestMapping(value = "/api/v1/wx/decodeUserInfo", method = RequestMethod.GET, produces = "application/json") public Map decodeUserInfo(@RequestParam(required = true,value = "encryptedData")String encryptedData, @RequestParam(required = true,defaultValue = "iv")String iv, @RequestParam(required = true,defaultValue = "sessionId")String sessionId){ System.out.println(encryptedData); System.out.println(iv); //从缓存中获取session_key Object wxSessionObj = redisUtil.get(sessionId); if(null == wxSessionObj){ return rtnParam(40008, null); } String wxSessionStr = (String)wxSessionObj; String sessionKey = wxSessionStr.split("#")[0]; try { AES aes = new AES(); byte[] resultByte = aes.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv)); if(null != resultByte && resultByte.length > 0){ String userInfo = new String(resultByte, "UTF-8"); return rtnParam(0, userInfo); } } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return rtnParam(50021, null); } } ================================================ FILE: src/main/java/com/weapp/controller/WxClubController.java ================================================ package com.weapp.controller; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.weapp.common.annotation.Api; import com.weapp.common.constant.ApiConstant; import com.weapp.common.util.MongoPageable; import com.weapp.entity.wxclub.Article; import com.weapp.service.WxClubService; /** * 微信小程序Club专栏文章接口 * @site http://www.wxappclub.com * @author xiaoqiang * */ @RestController public class WxClubController extends BaseController{ @Autowired private WxClubService wxClubService; /** * 专栏文章列表 * @param id * @param page * @return */ @Api(name = ApiConstant.WX_CLUB_ARTICLES) @RequestMapping(value = "/api/v1/wxclub/column/{id}/{page}", method = RequestMethod.GET, produces = "application/json") public MapgetArticles(@PathVariable String id,@PathVariable Integer page){ List
list = wxClubService.getByGroupPath("/column/" + id); return rtnParam(0, list); } /** * 文章搜索 * @param pageNo * @param searchText * @return */ @Api(name = ApiConstant.WX_CLUB_SEARCH) @RequestMapping(value = "/api/v1/wxclub/column/search/{pageNo}", method = RequestMethod.GET, produces = "application/json") public MapgetArticles(@PathVariable(value="pageNo",required=false) Integer pageNo, @RequestParam(required=false,value="text",defaultValue="")String searchText){ MongoPageable page = new MongoPageable(); page.setPagenumber(pageNo); List
list = wxClubService.getByTitle(searchText, page); return rtnParam(0, list); } } ================================================ FILE: src/main/java/com/weapp/entity/app/TUser.java ================================================ package com.weapp.entity.app; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * App用户实体类 * @author xiaoqiang * */ @Document(collection="t_users") @Data @NoArgsConstructor @AllArgsConstructor public class TUser { @Id private String id; private String appId; private String password; private String realName; private String nickName; private String gender; private String avatar; private String signature; private String wxOpenId; private String wxUnionId; private String birthday; private String address; private String createDate; private String updateDate; private String lastLoginDate; private Boolean disabled; private String phone; private Boolean isPhoneActive; private String email; private Boolean isEmailActive; } ================================================ FILE: src/main/java/com/weapp/entity/auth/AccessLog.java ================================================ package com.weapp.entity.auth; import java.util.Date; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * API访问日志 * @author xiaoqiang * */ @Document(collection="t_access_logs") @Data @NoArgsConstructor @AllArgsConstructor public class AccessLog { @Id private String id; /*api名称*/ private String apiName; /*接口路径*/ private String uri; /*访问时间*/ private Date accessDate; /*请求参数*/ private String reqParam; /*返回参数*/ private String resParam; /*异常内容*/ private String exp; public AccessLog(String apiName, String uri, Date accessDate, String reqParam, String resParam, String exp) { super(); this.apiName = apiName; this.uri = uri; this.accessDate = accessDate; this.reqParam = reqParam; this.resParam = resParam; this.exp = exp; } } ================================================ FILE: src/main/java/com/weapp/entity/auth/ApiInfo.java ================================================ package com.weapp.entity.auth; import java.util.Date; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * API接口管理 * @author xiaoqiang * */ @Document(collection="t_apis") @Data @NoArgsConstructor @AllArgsConstructor public class ApiInfo { @Id private String id; /*接口名称*/ private String name; /*接口地址*/ private String uri; /*权限之和 * Get = 1 * POST = 2 * PUT = 4 * DELETE = 8 */ private Integer crud; /*每天调用次数上限*/ private Integer accessLimit; /*版本号*/ private String version; /*是否可用*/ private boolean disabled; /*解密算法*/ private String algorithm; private Date createDate; public ApiInfo(String name,String uri, Integer accessLimit, String version, boolean disabled, String algorithm, Date createDate) { super(); this.name = name; this.uri = uri; this.accessLimit = accessLimit; this.version = version; this.disabled = disabled; this.algorithm = algorithm; this.createDate = createDate; } } ================================================ FILE: src/main/java/com/weapp/entity/auth/AppKey.java ================================================ package com.weapp.entity.auth; import java.util.Date; import java.util.Map; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * app认证实体 * @author xiaoqiang * */ @Document(collection="t_appkeys") @Data @NoArgsConstructor @AllArgsConstructor public class AppKey { @Id private String id; /*分发的应用ID*/ private String appId; /*密钥*/ private String secretKey; /*创建日期*/ private Date createDate; /*有效截止日期,为2030-01-01 00:00:00表示无日期限制*/ private Date validDate; /*应用权限等级*/ private String appGrade; /*是否禁用*/ private Boolean disabled; /*拥有的api,及调用次数上限*/ private Map> apis; public AppKey(String appId, String secretKey, Date createDate, Date validDate, String appGrade, Boolean disabled, Map> apis) { super(); this.appId = appId; this.secretKey = secretKey; this.createDate = createDate; this.validDate = validDate; this.appGrade = appGrade; this.disabled = disabled; this.apis = apis; } } ================================================ FILE: src/main/java/com/weapp/entity/wxclub/Article.java ================================================ package com.weapp.entity.wxclub; import java.util.Map; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Document(collection="t_articles") @Data @NoArgsConstructor @AllArgsConstructor public class Article { @Id private String id; private String path; private String groupPath; private String title; private String digest; private String createTime; private Integer browers; private Integer comments; private Map author; private String mark; private String content; } ================================================ FILE: src/main/java/com/weapp/interceptor/ApiInterceptor.java ================================================ package com.weapp.interceptor; import java.io.PrintWriter; import java.util.Date; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import com.google.common.collect.ImmutableMap; import com.weapp.entity.auth.AccessLog; import com.weapp.entity.auth.ApiInfo; import com.weapp.entity.auth.AppKey; import com.weapp.service.AccessLogService; import com.weapp.service.ApiInfoService; import com.weapp.service.AppKeyService; /** * api接口拦截处理 * @author xiaoqiang * */ public class ApiInterceptor implements HandlerInterceptor { private static ImmutableMapmethodMap = ImmutableMap.of("GET", 1, "POST", 2, "PUT", 4, "DELETE", 8); @Autowired private AppKeyService appKeyService; @Autowired private ApiInfoService apiInfoService; @Autowired private ImmutableMap errorCodeMap; @Autowired private AccessLogService accessLogService; @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object arg2, Exception exp) throws Exception { String apiName = request.getParameter("apiName"); if(!StringUtils.isEmpty(apiName)){ AccessLog accessLog = new AccessLog(); accessLog.setAccessDate(new Date()); accessLog.setApiName(apiName); accessLog.setUri(request.getRequestURI()); if(exp != null){ accessLog.setExp(exp.getMessage()); } //拼接请求参数,key1=value1&key2=value2的形式 String paramStr = ""; Map params = request.getParameterMap(); if (params != null && params.size() > 0) { for(Map.Entry p : params.entrySet()){ if(p.getValue() == null || p.getValue().length == 0){ continue; } paramStr += p.getKey() + "=" + p.getValue()[0] + "&"; } } accessLog.setReqParam(paramStr.substring(0, paramStr.length() - 1)); System.out.println(accessLog.getReqParam()); accessLogService.save(accessLog); } } @Override public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception { String apiName = request.getParameter("apiName"); if(null == apiName || "".equals(apiName)){ response.setStatus(HttpServletResponse.SC_NOT_FOUND); return false; } ApiInfo apiInfo = apiInfoService.getByApiName(apiName); if(null == apiInfo){ response.setStatus(HttpServletResponse.SC_NOT_FOUND); return false; } response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter out = null; //判断接口状态; if(apiInfo.isDisabled()){ //接口已禁用 out = response.getWriter(); out.append(getResStr("40003")); out.flush(); out.close(); return false; } String method = request.getMethod(); if(Integer.compare(methodMap.get(method), apiInfo.getCrud()) != 0){ //http method不匹配 apiInfo.getCrud() out = response.getWriter(); out.append(getResStr("40005")); out.flush(); out.close(); return false; } String appId = request.getParameter("appId"); if(null == appId || "".equals(appId)){ out = response.getWriter(); out.append(getResStr("40001")); out.flush(); out.close(); return false; } //获取appid,请求是否合法 AppKey appKey = appKeyService.getByAppId(appId); if(null == appKey){ out = response.getWriter(); out.append(getResStr("40001")); out.flush(); out.close(); return false; } //判断是否有接口调用权限 Map>apiMap = appKey.getApis(); if(null == apiMap || apiMap.size() == 0 || !apiMap.containsKey(apiName)){ //无调用权限 out = response.getWriter(); out.append(getResStr("40006")); out.flush(); out.close(); return false; } //调用次数是否超出上限; MapmethodInfo = apiMap.get(apiName); if(methodInfo.get("calltimes") > methodInfo.get("alltimes")){ //超出调用次数 out = response.getWriter(); out.append(getResStr("40007")); out.flush(); out.close(); return false; } //参数校验 //通过,更新调用次数 methodInfo.put("calltimes", methodInfo.get("calltimes") + 1); apiMap.put(apiName, methodInfo); appKey.setApis(apiMap); //记录访问日志 appKeyService.update(appKey); return true; } private String getResStr(String errorCode){ return "{\"errorCode\":" + errorCode + ",\"msg\":\"" + errorCodeMap.get(errorCode) + "\"}"; } } ================================================ FILE: src/main/java/com/weapp/interceptor/ApiWebConfigure.java ================================================ package com.weapp.interceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * API接口拦截配置 * @author xiaoqiang * */ @Configuration public class ApiWebConfigure extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry){ registry.addInterceptor(getApiInterceptor()) .addPathPatterns("/api/**"); } @Bean public ApiInterceptor getApiInterceptor(){ return new ApiInterceptor(); } } ================================================ FILE: src/main/java/com/weapp/listener/ApiServletContextListener.java ================================================ package com.weapp.listener; import java.io.IOException; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.weapp.common.annotation.Api; import com.weapp.entity.auth.ApiInfo; import com.weapp.service.ApiInfoService; import com.weapp.service.WxClubService; /** * Get == 1 * POST == 2 * PUT == 4 * DELETE == 8 * @author xiaoqiang * */ @WebListener @Component public class ApiServletContextListener implements ServletContextListener { private static ImmutableMapmethodMap = ImmutableMap.of("GET", 1, "POST", 2, "PUT", 4, "DELETE", 8); @Override public void contextDestroyed(ServletContextEvent arg0) { System.out.println("destory"); } @Override public void contextInitialized(ServletContextEvent arg0) { WebApplicationContext wc = WebApplicationContextUtils.getRequiredWebApplicationContext(arg0.getServletContext()); ApiInfoService apiInfoService = wc.getBean(ApiInfoService.class); WxClubService clubService = wc.getBean(WxClubService.class); RequestMappingHandlerMapping rmhp = wc.getBean(RequestMappingHandlerMapping.class); Map map = rmhp.getHandlerMethods(); ListapiInfolist = Lists.newArrayList(); ApiInfo apiInfo = null; Date curDate = new Date(); for(RequestMappingInfo info : map.keySet()){ RequestMethodsRequestCondition requestMethodsRequestCondition = info.getMethodsCondition(); Setmethods = requestMethodsRequestCondition.getMethods(); if(methods.size() == 0){ continue; } HandlerMethod handlerMethod = map.get(info); if(!handlerMethod.hasMethodAnnotation(Api.class)){ continue; } Api api = handlerMethod.getMethodAnnotation(Api.class); apiInfo = new ApiInfo(); apiInfo.setUri(info.getPatternsCondition().getPatterns().toArray()[0].toString()); apiInfo.setVersion(api.version()); apiInfo.setName(api.name()); apiInfo.setDisabled(api.disabled()); apiInfo.setAlgorithm(api.algorithm()); apiInfo.setAccessLimit(api.accessLimit()); apiInfo.setCrud(methodMap.get(methods.toArray()[0].toString())); apiInfo.setCreateDate(curDate); apiInfolist.add(apiInfo); } apiInfoService.deleteAll(); apiInfoService.saveList(apiInfolist); clubService.deleteAll(); try { clubService.getWxClubColumn(); } catch (IOException e) { e.printStackTrace(); } } } ================================================ FILE: src/main/java/com/weapp/redis/MyRedisCacheConfig.java ================================================ package com.weapp.redis; import java.lang.reflect.Method; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; @Configuration @EnableCaching public class MyRedisCacheConfig extends CachingConfigurerSupport { @SuppressWarnings({ "rawtypes", "unchecked" }) @Bean public RedisTemplate redisTemplate( RedisConnectionFactory factory) { RedisTemplate template = new StringRedisTemplate(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } @Bean public KeyGenerator wiselyKeyGenerator(){ return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } @Bean public CacheManager cacheManager( @SuppressWarnings("rawtypes") RedisTemplate redisTemplate) { return new RedisCacheManager(redisTemplate); } } ================================================ FILE: src/main/java/com/weapp/redis/RedisUtil.java ================================================ package com.weapp.redis; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.stereotype.Service; import org.springframework.util.Assert; /** * Jedis增删改查操作 * @author xiaoqiang * * @param */ @Service public class RedisUtil { @Autowired(required=true) private RedisTemplate redisTemplate; /** * 添加对象 */ public boolean add(final String key, final String value) { redisTemplate.execute(new RedisCallback() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { connection.set( redisTemplate.getStringSerializer().serialize(key), redisTemplate.getStringSerializer().serialize(value)); return true; } }); return false; } /** * 添加对象 */ public boolean add(final String key, final Long expires, final String value) { redisTemplate.execute(new RedisCallback() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { connection.setEx( redisTemplate.getStringSerializer().serialize(key), expires, redisTemplate.getStringSerializer().serialize(value) ); return true; } }); return false; } /** * 添加Map */ public boolean add(final Mapmap) { Assert.notEmpty(map); boolean result = redisTemplate.execute(new RedisCallback() { public Boolean doInRedis(RedisConnection connection) throws DataAccessException { RedisSerializer serializer = redisTemplate.getStringSerializer(); for (Map.Entry entry : map.entrySet()) { byte[] key = serializer.serialize(entry.getKey()); byte[] name = serializer.serialize(entry.getValue()); connection.setNX(key, name); } return true; } }, false, true); return result; } /** * 删除对象 ,依赖key */ public void delete(String key) { redisTemplate.delete(key); } /** * 修改对象 */ public boolean update(final String key,final String value) { if (get(key) == null) { throw new NullPointerException("数据行不存在, key = " + key); } boolean result = redisTemplate.execute(new RedisCallback() { public Boolean doInRedis(RedisConnection connection) throws DataAccessException { RedisSerializer serializer = redisTemplate.getStringSerializer(); connection.set(serializer.serialize(key), serializer.serialize(value)); return true; } }); return result; } /** * 根据key获取对象 */ public Object get(final String keyId) { Object result = redisTemplate.execute(new RedisCallback() { public Object doInRedis(RedisConnection connection) throws DataAccessException { RedisSerializer serializer = redisTemplate.getStringSerializer(); byte[] key = serializer.serialize(keyId); byte[] value = connection.get(key); if (value == null) { return null; } return serializer.deserialize(value); } }); return result; } } ================================================ FILE: src/main/java/com/weapp/repository/AccessLogRepository.java ================================================ package com.weapp.repository; import org.springframework.data.mongodb.repository.MongoRepository; import com.weapp.entity.auth.AccessLog; /** * 访问日志操作 * @author xiaoqiang * */ public interface AccessLogRepository extends MongoRepository { } ================================================ FILE: src/main/java/com/weapp/repository/ApiInfoRepository.java ================================================ package com.weapp.repository; import org.springframework.data.mongodb.repository.MongoRepository; import com.weapp.entity.auth.ApiInfo; /** * api管理操作 * @author xiaoqiang * */ public interface ApiInfoRepository extends MongoRepository { ApiInfo findByName(String apiName); } ================================================ FILE: src/main/java/com/weapp/repository/AppKeyRepository.java ================================================ package com.weapp.repository; import org.springframework.data.mongodb.repository.MongoRepository; import com.weapp.entity.auth.AppKey; /** * appkey管理操作 * @author xiaoqiang * */ public interface AppKeyRepository extends MongoRepository { AppKey findByAppId(String appId); } ================================================ FILE: src/main/java/com/weapp/repository/WxClubRepository.java ================================================ package com.weapp.repository; import java.util.List; import org.springframework.data.mongodb.repository.MongoRepository; import com.weapp.common.util.MongoPageable; import com.weapp.entity.wxclub.Article; public interface WxClubRepository extends MongoRepository { List
findByGroupPath(String groupPath); List
findByTitleLike(String title, MongoPageable page); } ================================================ FILE: src/main/java/com/weapp/service/AccessLogService.java ================================================ package com.weapp.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.weapp.entity.auth.AccessLog; import com.weapp.repository.AccessLogRepository; @Service @Transactional(readOnly=true) public class AccessLogService { @Autowired private AccessLogRepository accessLogRepository; @Transactional(readOnly=false) public void save(AccessLog accessLog) { accessLogRepository.save(accessLog); } } ================================================ FILE: src/main/java/com/weapp/service/ApiInfoService.java ================================================ package com.weapp.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.weapp.entity.auth.ApiInfo; import com.weapp.repository.ApiInfoRepository; @Transactional(readOnly=false) @Service public class ApiInfoService { @Autowired private ApiInfoRepository apiInfoRepository; public void deleteAll(){ apiInfoRepository.deleteAll(); } public void saveList(Iterablelist){ apiInfoRepository.save(list); } public ApiInfo getByApiName(String apiName){ return apiInfoRepository.findByName(apiName); } } ================================================ FILE: src/main/java/com/weapp/service/AppKeyService.java ================================================ package com.weapp.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Update; import static org.springframework.data.mongodb.core.query.Criteria.where; import static org.springframework.data.mongodb.core.query.Query.query; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.weapp.entity.auth.AppKey; import com.weapp.repository.AppKeyRepository; @Service @Transactional(readOnly=true) public class AppKeyService { @Autowired AppKeyRepository appKeyRepository; @Autowired MongoTemplate mongoTemplate; public AppKey getByAppId(String appId){ return appKeyRepository.findByAppId(appId); } public void update(AppKey appKey){ mongoTemplate.updateFirst(query(where("_id").is(appKey.getId())), Update.update("apis", appKey.getApis()), AppKey.class); } } ================================================ FILE: src/main/java/com/weapp/service/WxClubService.java ================================================ package com.weapp.service; import java.io.IOException; import java.util.List; import java.util.Map; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.weapp.common.util.MongoPageable; import com.weapp.entity.wxclub.Article; import com.weapp.repository.WxClubRepository; @Service public class WxClubService { @Autowired private WxClubRepository clubRepository; private static final String CLUBHOST = "http://www.wxappclub.com"; private static final String WX_CLUB_HOST = "http://www.wxappclub.com"; private static final String[] paths = {"/column/1","/column/2","/column/3","/column/4","/column/5","/column/6","/column/7","/column/8","/column/10"}; //抓取小程序专栏内容 public void getWxClubColumn() throws IOException{ List
list = Lists.newArrayList(); for(String path: paths){ Document document = Jsoup.connect(WX_CLUB_HOST + path).get(); if(document == null){ continue; } Elements els = document.select(".topic_list li"); if(els == null || els.size() == 0){ continue; } String detailPath = ""; String title = ""; String desc = ""; String content = ""; Map authorMap = null; String createTime = ""; String comment = "0"; String brower = "0"; String remark = ""; for(Element element : els){ Element titleEle = element.select(".topic_title a").first(); detailPath = titleEle.absUrl("href"); title = titleEle.text(); authorMap = Maps.newHashMap(); Document detailDoc = Jsoup.connect(detailPath).get(); if(detailDoc != null){ Element detailEl = detailDoc.select(".topic_content").first(); Elements imgEls = detailEl.select("img[src]"); for (Element el : imgEls) { String imgUrl = el.attr("src"); imgUrl =CLUBHOST + "/" +imgUrl; el.attr("src", imgUrl); } content = detailEl.html(); String headimg = detailDoc.select(".panel-body p .avatar").first().absUrl("src"); String nickname = detailDoc.select(".panel-body p .username").first().text(); Elements markEls = detailDoc.select(".panel-body .userremark"); if(markEls != null){ remark = markEls.first().text(); } authorMap.put("headimg", headimg); authorMap.put("nickname", nickname); authorMap.put("remark", remark); } Elements descEls = element.select(".topic_desc"); if(descEls != null && descEls.size() > 0){ if(descEls.size() == 1){ createTime = descEls.get(0).select(".last_time").first().text(); Elements elss = descEls.get(0).select(".reply_count .count_of_visits"); if(elss != null && elss.size() == 2){ comment = elss.get(0).text().replace("评论", "").replace(" ", ""); brower = elss.get(1).text().replace("浏览", "").replace(" ", ""); } }else { desc = descEls.get(0).text(); createTime = descEls.get(1).select(".last_time").first().text(); Elements elss = descEls.get(1).select(".reply_count .count_of_visits"); if(elss != null && elss.size() == 2){ comment = elss.get(0).text().replace("评论", "").replace(" ", ""); brower = elss.get(1).text().replace("浏览", "").replace(" ", ""); } } } Article article = new Article(); article.setAuthor(authorMap); article.setPath(detailPath); article.setGroupPath(path); article.setBrowers(Integer.valueOf(brower)); article.setComments(Integer.valueOf(comment)); article.setContent(content); article.setCreateTime(createTime); article.setDigest(desc); article.setTitle(title); list.add(article); detailPath = ""; title = ""; desc = ""; authorMap = null; createTime = ""; comment = "0"; brower = "0"; content = ""; } } clubRepository.save(list); } /** * 清空 */ public void deleteAll() { clubRepository.deleteAll(); } /** * 根据专栏ID搜索 * @param groupPath * @return */ public List
getByGroupPath(String groupPath) { return clubRepository.findByGroupPath(groupPath); } /** * 根据文章标题模糊搜索(分页) * @param title * @param page * @return */ public List
getByTitle(String title, MongoPageable page) { return clubRepository.findByTitleLike(title,page); } } ================================================ FILE: src/main/java/com/weapp/service/WxService.java ================================================ package com.weapp.service; import java.util.Map; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.alibaba.fastjson.JSON; import com.weapp.common.properties.WxAuth; import com.weapp.common.util.HttpRequest; import com.weapp.redis.RedisUtil; @Service public class WxService { @Autowired private WxAuth wxAuth; @Autowired private RedisUtil redisUtil; /** * 根据小程序登录返回的code获取openid和session_key * https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-login.html?t=20161107 * @param wxcode * @return */ @SuppressWarnings("unchecked") public MapgetWxSession(String wxCode){ StringBuffer sb = new StringBuffer(); sb.append("appid=").append(wxAuth.getAppId()); sb.append("&secret=").append(wxAuth.getSecret()); sb.append("&js_code=").append(wxCode); sb.append("&grant_type=").append(wxAuth.getGrantType()); String res = HttpRequest.sendGet(wxAuth.getSessionHost(), sb.toString()); if(res == null || res.equals("")){ return null; } return JSON.parseObject(res, Map.class); } /** * 缓存微信openId和session_key * @param wxOpenId 微信用户唯一标识 * @param wxSessionKey 微信服务器会话密钥 * @param expires 会话有效期, 以秒为单位, 例如2592000代表会话有效期为30天 * @return */ public String create3rdSession(String wxOpenId, String wxSessionKey, Long expires){ String thirdSessionKey = RandomStringUtils.randomAlphanumeric(64); StringBuffer sb = new StringBuffer(); sb.append(wxSessionKey).append("#").append(wxOpenId); redisUtil.add(thirdSessionKey, expires, sb.toString()); return thirdSessionKey; } } ================================================ FILE: src/main/java/com/weapp/webconfig/WebConfigurer.java ================================================ /** * * @project xundaowei * @filename WebConfigurer.java * @date 2016年12月8日 * @author KeShanqiang * */ package com.weapp.webconfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Component public class WebConfigurer extends WebMvcConfigurerAdapter { @Value("${img.local.path}") private String imgPath; public void addResourceHandlers(ResourceHandlerRegistry registry) { String localPath = "file://" + imgPath; String osName = System.getProperty("os.name"); //判断操作系统类型 if(osName.toLowerCase().contains("win")){ localPath += "/"; } registry.addResourceHandler("/upload/**").addResourceLocations(localPath); } } ================================================ FILE: src/main/java/com/weapp/websocket/ChatWebSocketHandler.java ================================================ package com.weapp.websocket; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; /** * 微信小程序WebSocket * @author Shanqinag Ke * @since 2016-10-15 */ public class ChatWebSocketHandler extends TextWebSocketHandler{ private static final Logger logger = LoggerFactory.getLogger(ChatWebSocketHandler.class); private final static List sessions = Collections.synchronizedList(new ArrayList()); /** * 处理接收文本 */ @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String cont = message.getPayload(); if(StringUtils.isEmpty(cont)){ return; } //校验JSON格式 if(!cont.startsWith("{") || !cont.endsWith("}")){ return; } JSONObject json = JSON.parseObject(cont); if(!json.containsKey("user") || !json.containsKey("content")){ return; } try { //发送消息 sendChatMessage(json.getString("user"), json.getString("content")); super.handleTextMessage(session, message); } catch (Exception e) { e.printStackTrace(); } } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { logger.debug("connect to the websocket chat success......"); sessions.add(session); } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { if(session.isOpen()){ session.close(); } logger.debug("websocket chat connection closed......"); sessions.remove(session); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { logger.debug("websocket chat connection closed......"); sessions.remove(session); } @Override public boolean supportsPartialMessages() { return false; } /** * 判断用户是否在线 * @param userName 登录用户名 * @return */ public boolean isUserConnected(String userName){ if(org.springframework.util.StringUtils.isEmpty(userName)){ return false; } for (WebSocketSession user : sessions) { if (user.getAttributes().get("user").equals(userName)) { return true; } } return false; } /** * 发送消息 * @param userName 昵称 * @param content 发送内容 */ public static void sendChatMessage(String userName,String content){ if(StringUtils.isEmpty(userName)){ return; } if (content == null){ return; } for (WebSocketSession session : sessions) { if (!session.getAttributes().get("user").equals(userName)) { if(session.isOpen()){ try{ Map retMap = new HashMap(); retMap.put("user",userName); retMap.put("content",content); session.sendMessage(new TextMessage(JSON.toJSONString(retMap))); }catch (IOException ioe){ ioe.printStackTrace(); } } break; } } } } ================================================ FILE: src/main/java/com/weapp/websocket/WebSocketConfig.java ================================================ package com.weapp.websocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; /** * 配置websocket * @author Shanqinag Ke * @since 2016-10-15 */ @Configuration @EnableWebMvc @EnableWebSocket public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{ @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { //注册通道 registry.addHandler(chatWebSocketHandler(),"/websocket").setAllowedOrigins("*").addInterceptors(myInterceptor()); registry.addHandler(chatWebSocketHandler(), "/sockjs/websocket").setAllowedOrigins("*").addInterceptors(myInterceptor()).withSockJS(); } //消息处理Handler @Bean public ChatWebSocketHandler chatWebSocketHandler() { return new ChatWebSocketHandler(); } //websocket拦截器 @Bean public WebSocketHandshakeInterceptor myInterceptor(){ return new WebSocketHandshakeInterceptor(); } } ================================================ FILE: src/main/java/com/weapp/websocket/WebSocketHandshakeInterceptor.java ================================================ package com.weapp.websocket; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; import com.alibaba.fastjson.JSON; /** * websocket拦截器 * @author Shanqinag Ke * @since 2016-10-15 */ public class WebSocketHandshakeInterceptor extends HttpSessionHandshakeInterceptor implements HandshakeInterceptor{ @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { if (request instanceof ServletServerHttpRequest) { //解决The extension [x-webkit-deflate-frame] is not supported问题 if(request.getHeaders().containsKey("Sec-WebSocket-Extensions")) { request.getHeaders().set("Sec-WebSocket-Extensions", "permessage-deflate"); } ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; HttpSession session = servletRequest.getServletRequest().getSession(); if (session != null) { //使用user区分WebSocketHandler,以便定向发送消息 HttpServletRequest req = servletRequest.getServletRequest(); String name = req.getParameter("name"); System.out.println(JSON.toJSONString(req.getParameterNames())); if(null != name && !"".equals(name)){ attributes.put("user", name); } } } return super.beforeHandshake(request, response, wsHandler, attributes); } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { super.afterHandshake(request, response, wsHandler, exception); } } ================================================ FILE: src/main/resources/application.properties ================================================ #server server.context-path=/weappservice server.display-name=weappservice server.port=8443 server.ssl.key-store=classpath:https-site.jks server.ssl.key-store-password=xxx server.ssl.key-password=xxx #file img.host=http://localhost/wximg img.local.path=/usr/share/nginx/html/wximg api.v1=/api/v1 #mongodb config spring.data.mongodb.uri=mongodb://localhost:27017/test # REDIS (RedisProperties) spring.redis.database=0 spring.redis.host=xxx.xxx.xxx.xxx spring.redis.password=xxx spring.redis.port=6379 spring.redis.pool.max-idle=20 spring.redis.pool.min-idle=5 spring.redis.pool.max-active=100 spring.redis.pool.max-wait=1000 #open aop spring.aop.auto=true wxapp.sessionHost=https://api.weixin.qq.com/sns/jscode2session wxapp.appId=xxx wxapp.secret=xxx wxapp.grantType=authorization_code ================================================ FILE: src/main/resources/error_code.properties ================================================ 0= 40001=\u4E0D\u5408\u6CD5\u7684appId 40002=\u63A5\u53E3\u4E0D\u5B58\u5728 40003=\u63A5\u53E3\u88AB\u7981\u6B62\u8C03\u7528 40005=Http Method\u4E0D\u5339\u914D 40006=\u63A5\u53E3\u672A\u6388\u6743 40007=\u9891\u7387\u8D85\u9650 40008=\u7528\u6237\u8EAB\u4EFD\u5DF2\u8FC7\u671F 40010=\u8BF7\u9009\u62E9\u4E0A\u4F20\u6587\u4EF6 40011=\u4E0A\u4F20\u5931\u8D25 50010=\u4E0E\u7B2C\u4E09\u65B9\u901A\u8BAF\u5931\u8D25 50020=\u83B7\u53D6\u5FAE\u4FE1session_key\u5931\u8D25 50021=\u7528\u6237\u654F\u611F\u6570\u636E\u89E3\u5BC6\u5931\u8D25 ================================================ FILE: src/main/resources/log4j.properties ================================================ log4j.rootCategory=info,stdout,logfile #stdout configure log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern= %d %p [%c] - <%m>%n #logfile configure log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender log4j.appender.logfile.File=../logs/server.log log4j.appender.logfile.layout=org.apache.log4j.PatternLayout log4j.appender.logfile.layout.ConversionPattern= %d %p [%c] - %m%n log4j.logger.java.sql.PreparedStatement=DEBUG ================================================ FILE: src/test/java/com/weapp/ApplicationTest.java ================================================ package com.weapp; import static org.hamcrest.Matchers.equalTo; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mock.web.MockServletContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import com.weapp.controller.AppUserController; @ActiveProfiles("test") @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = MockServletContext.class) @WebAppConfiguration @TestPropertySource("classpath:application-test.properties") public class ApplicationTest { private MockMvc mvc; @Before public void setUp() throws Exception { mvc = MockMvcBuilders.standaloneSetup(new AppUserController()).build(); MockitoAnnotations.initMocks(this); } //测试获取数据接口 @Test public void test() throws Exception{ RequestBuilder request = null; request = MockMvcRequestBuilders.get("/api/v1/user/123123") .param("appId", "test123").param("apiName", "GET_USER"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("{\"id\":\"123123\"}"))); } } ================================================ FILE: src/test/resources/application-test.properties ================================================ server.port=9090 server.context-path=/weappservice server.display-name=weappservice imgPath=http://120.26.231.155/wximg/ localPath=/usr/share/nginx/html/wximg/ api.v1=/api/v1 spring.data.mongodb.uri=mongodb://localhost:27017/test spring.aop.auto=true