1. common项目
fastjosn
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
注解
注解的定义
@Target(ElementType.METHOD) //用在方法上
@Retention(RetentionPolicy.RUNTIME) //运行的时候
@Documented //有文档
public @interface SysLogger {
String value() default "";
}
注解的逻辑和使用
- 在user-service代码
@Aspect // 进行切割
@Component //给spring管理
//切的位置
@Pointcut("@annotation(com.forezp.annotation.SysLogger)")
//之前进行操作
@Before("loggerPointCut()")
//得到 反射的自然
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//得到 放射的方法
Method method = signature.getMethod();
String methodName = signature.getName();
//从注解上,得到 SysLogger
SysLogger sysLogger = method.getAnnotation(SysLogger.class);
//得到描述
sysLogger.value()
//请求的方法名 类的路径
String className = joinPoint.getTarget().getClass().getName();
@Aspect // 进行切割
@Component //给spring管理
public class SysLoggerAspect {
@Autowired
private LoggerService loggerService;
//切的位置
@Pointcut("@annotation(com.forezp.annotation.SysLogger)")
public void loggerPointCut() {
}
//之前进行操作
@Before("loggerPointCut()")
public void saveSysLog(JoinPoint joinPoint) {
//得到 反射的自然
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//得到 放射的方法
Method method = signature.getMethod();
//创建log
SysLog sysLog = new SysLog();
//从注解上,得到 SysLogger
SysLogger sysLogger = method.getAnnotation(SysLogger.class);
if(sysLogger != null){
//得到:注解上的描述
sysLog.setOperation(sysLogger.value());
}
//请求的方法名 类的路径
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
//拼接成 完整的方法
sysLog.setMethod(className + "." + methodName + "()");
//请求的参数
Object[] args = joinPoint.getArgs();
String params="";
for(Object o:args){
//对象转成 json,拼接
params+=JSON.toJSONString(o);
}
//如果 参数不为空
if(!StringUtils.isEmpty(params)) {
//设置上参数
sysLog.setParams(params);
}
//设置IP地址
sysLog.setIp(HttpUtils.getIpAddress());
//用户名
String username = UserUtils.getCurrentPrinciple();
if(!StringUtils.isEmpty(username)) {
//设置上用户名
sysLog.setUsername(username);
}
//日志的创建时间
sysLog.setCreateDate(new Date());
//保存系统日志
loggerService.log(sysLog);
}
}
@SysLogger("registry")
dto 和 exception
public class RespDTO<T> implements Serializable{
public int code = 0;
public String error = "";
public T data;
public static RespDTO onSuc(Object data) {
RespDTO resp = new RespDTO();
resp.data = data;
return resp;
}
}
exception
异常的定义
public class CommonException extends RuntimeException {
private ErrorCode errorCode;
public CommonException(ErrorCode errorCode) {
super(errorCode.getMsg());
this.errorCode = errorCode;
}
public CommonException(ErrorCode errorCode, String msg) {
super(msg);
this.errorCode = errorCode;
}
public ErrorCode getErrorCode()
public int getCode()
public String getMsg()
return errorCode.XXX(如:getMsg())
}
异常的 枚举
public enum ErrorCode {
OK(0, ""),
FAIL(-1, "操作失败"),
RPC_ERROR(-2,"远程调度失败"),
USER_NOT_FOUND(1000,"用户不存在"),
USER_PASSWORD_ERROR(1001,"密码错误"),
GET_TOKEN_FAIL(1002,"获取token失败"),
TOKEN_IS_NOT_MATCH_USER(1003,"请使用自己的token进行接口请求"),
BLOG_IS_NOT_EXIST(2001,"该博客不存在")
;
private int code;
private String msg;
ErrorCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode()
public String getMsg()
//根据 code,获取 到,这个枚举
public static ErrorCode codeOf(int code) {
for (ErrorCode state : values()) {
if (state.getCode() == code) {
return state;
}
}
return null;
}
}
异常的逻辑处理 和 使用
@ControllerAdvice //想controller 返回
@ResponseBody
public class CommonExceptionHandler {
@ExceptionHandler(CommonException.class) //切这个异常
public ResponseEntity<RespDTO> handleException(Exception e) {
RespDTO resp = new RespDTO();
//强转
CommonException taiChiException = (CommonException) e;
resp.code = taiChiException.getCode();
resp.error = e.getMessage();
//返回
return new ResponseEntity(resp, HttpStatus.OK);
}
}
if(null==jwt){
throw new CommonException(ErrorCode.GET_TOKEN_FAIL);
}
2. blog-service
pom 和 yaml 和 main
<dependencies>
<dependency>
<groupId>com.forezp</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
starter-netflix-eureka-client
starter-config
starter-web
starter-openfeign
starter-actuator
starter-netflix-hystrix-dashboard
starter-netflix-hystrix
starter-sleuth
starter-zipkin
springfox-swagger2
springfox-swagger-ui.0
mysql-connector-java
starter-data-jpa
<!--security-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
starter-amqp
</dependencies>
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrixDashboard
@EnableHystrix
spring:
application:
name: blog-service
cloud:
config:
uri: http://localhost:8769
fail-fast: true
profiles:
active: pro
# zipkin:
# base-url: http://localhost:9411
#
# datasource:
# driver-class-name: com.mysql.jdbc.Driver
# url: jdbc:mysql://localhost:3306/sys_blog?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
# username: root
# password: 123456
# jpa:
# hibernate:
# ddl-auto: update
# show-sql: true
#
# rabbitmq:
# host: localhost
# port: 5672
# username: guest
# password: guest
# publisher-confirms: true
# virtual-host: /
- public.cert 省略
其他包说明
- aop 一样
- config包下的:
- GlobalMethodSecurityConfiguration
- JwtConfiguration
- RabbitConfig
- ResourceServerConfiguration
- SwaggerConfig
client 包,就是feign包
@FeignClient(value = "user-service",fallback = UserServiceHystrix.class )
public interface UserServiceClient {
@PostMapping(value = "/user/{username}")
RespDTO<User> getUser(@RequestHeader(value = "Authorization") String token, @PathVariable("username") String username);
}
@Component
public class UserServiceHystrix implements UserServiceClient {
@Override
public RespDTO<User> getUser(String token, String username) {
System.out.println(token);
System.out.println(username);
return null;
}
}
dao
public interface BlogDao extends JpaRepository<Blog, Long> {
List<Blog> findByUsername(String username);
}
service
@Service
public class LoggerService {
@Autowired
private AmqpTemplate rabbitTemplate;
public void log(SysLog sysLog){
rabbitTemplate.convertAndSend(RabbitConfig.queueName, JSON.toJSONString(sysLog));
}
}
@Service
public class BlogService {
@Autowired
BlogDao blogDao;
@Autowired
UserServiceClient userServiceClient;
//保存
public Blog postBlog(Blog blog) {
return blogDao.save(blog);
}
//查找
public List<Blog> findBlogs(String username) {
return blogDao.findByUsername(username);
}
//根据ID查找
public BlogDetailDTO findBlogDetail(Long id) {
//查询 blog
Optional<Blog> blogOptional = blogDao.findById(id);
Blog blog=null;
//if(blogOptional!=null){ 这是错误的代码,永远为true
//如果 博客存在,就得到它
//blog=blogOptional.get(); //会报错
//}
if(blogOptional.isPresent()){
blog=blogOptional.get();
}
if (null == blog) {
throw new CommonException(ErrorCode.BLOG_IS_NOT_EXIST);
}
RespDTO<User> respDTO = userServiceClient.getUser(UserUtils.getCurrentToken(), blog.getUsername());
if (respDTO==null) {
throw new CommonException(ErrorCode.RPC_ERROR);
}
BlogDetailDTO blogDetailDTO = new BlogDetailDTO();
blogDetailDTO.setBlog(blog);
blogDetailDTO.setUser(respDTO.data);
return blogDetailDTO;
}
}
Entity
public class BlogDetailDTO {
//get set
private Blog blog;
private User user;
}
@Entity
public class Blog implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String username;
@Column
private String title;
@Column
private String suject;
}
public class SysLog {
private Long id;
//用户名
private String username;
//用户操作
private String operation;
//请求方法
private String method;
//请求参数
private String params;
//IP地址
private String ip;
//创建时间
private Date createDate;
}
public class User {
private Long id;
private String username;
private String password;
}
- util 不变
web
@RestController
@RequestMapping("/blog")
public class BlogController {
@Autowired
BlogService blogService;
@ApiOperation(value = "发布博客", notes = "发布博客")
@PreAuthorize("hasRole('USER')") //user权限,发布
@PostMapping("")
@SysLogger("postBlog")
public RespDTO postBlog(@RequestBody Blog blog){
//字段判读省略
Blog blog1= blogService.postBlog(blog);
return RespDTO.onSuc(blog1);
}
@ApiOperation(value = "根据用户id获取所有的blog", notes = "根据用户id获取所有的blog")
@PreAuthorize("hasAuthority('ROLE_USER')") //必须User权限的 另一种写法
@GetMapping("/{username}")
@SysLogger("getBlogs")
public RespDTO getBlogs(@PathVariable String username){
//字段判读省略
if(UserUtils.isMyself(username)) {
List<Blog> blogs = blogService.findBlogs(username);
return RespDTO.onSuc(blogs);
}else {
throw new CommonException(ErrorCode.TOKEN_IS_NOT_MATCH_USER);
}
}
@ApiOperation(value = "获取博文的详细信息", notes = "获取博文的详细信息")
@PreAuthorize("hasAuthority('ROLE_USER')") //获得细节
@GetMapping("/{id}/detail")
@SysLogger("getBlogDetail")
public RespDTO getBlogDetail(@PathVariable Long id){
return RespDTO.onSuc(blogService.findBlogDetail(id));
}
}
public class RespDTO<T> implements Serializable{
public int code = 0;
public String error = "";
public T data;
public static RespDTO onSuc(Object data) {
RespDTO resp = new RespDTO();
resp.data = data;
return resp;
}
}
public BlogDetailDTO findBlogDetail(Long id){
BlogDetailDTO blogDetailDTO = new BlogDetailDTO();
blogDetailDTO.setBlog(blog);
blogDetailDTO.setUser(respDTO.data);
return blogDetailDTO;
}
public class BlogDetailDTO {
private Blog blog;
private User user;
}
3. log-service
pom 和 yaml 和 main
common 自己写的
starter-config
starter-actuator
starter-netflix-eureka-client
starter-web
spring-security-jwt
spring-security-oauth2
mysql-connector-java
starter-data-jpa
starter-amqp
spring:
application:
name: logger-service
cloud:
config:
uri: http://localhost:8769
fail-fast: true
profiles:
active: pro
- public.cert 不变
@EnableEurekaClient
@EnableDiscoveryClient
其他包 config
- GlobalMethodSecurityConfiguration
- JwtConfiguration
- RabbitConfig 增加了东西
- ResourceServerConfiguration
public interface SysLogDAO extends JpaRepository<SysLog, Long> {
}
@Entity
public class SysLog implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//用户名
@Column
private String username;
//用户操作
@Column
private String operation;
//请求方法
@Column
private String method;
//请求参数
@Column
private String params;
//IP地址
@Column
private String ip;
//创建时间
@Column
private Date createDate;
}
rabbit 消息监听者 实现
//每收到一个消息,就会 走这个方法
@Component
public class Receiver {
private CountDownLatch latch = new CountDownLatch(1);
@Autowired
SysLogService sysLogService;
public void receiveMessage(String message) {
System.out.println("Received <" + message + ">");
//收到的消息,转成 syslog
SysLog sysLog= JSON.parseObject(message,SysLog.class);
//保存日志
sysLogService.saveLogger(sysLog);
latch.countDown(); //释放信号量
}
}
CountDownLatch 信号量的作用,只有其他的线程 完成了一系列的操作,释放信号后,其他被阻塞的线程 获取到 信号才能被唤醒
@Component
public class Sender {
@Autowired
private AmqpTemplate rabbitTemplate;
public void send() {
String context = "hello " + new Date();
System.out.println("Sender : " + context);
//发送消息
rabbitTemplate.convertAndSend(RabbitConfig.queueName, "Hello from RabbitMQ!");
}
}
public class User {
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
service
@Service
public class SysLogService {
@Autowired
SysLogDAO sysLogDAO;
public void saveLogger(SysLog sysLog){
sysLogDAO.save(sysLog);
}
}
rabbitMq config
@Configuration
public class RabbitConfig {
public final static String queueName = "spring-boot";
@Bean
Queue queue() {
return new Queue(queueName, false);
}
@Bean
TopicExchange exchange() {
return new TopicExchange("spring-boot-exchange");
}
@Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(queueName);
}
@Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(queueName);
container.setMessageListener(listenerAdapter);
return container;
}
@Bean //把receiveMessage类,传递到 消息监听者 中
MessageListenerAdapter listenerAdapter(Receiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
}
4. SQL
sys-blog.sql
CREATE DATABASE `sys-blog` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
use `sys-blog`;
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for blog
-- ----------------------------
DROP TABLE IF EXISTS `blog`;
CREATE TABLE `blog` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`suject` varchar(255) DEFAULT NULL,
`title` varchar(255) DEFAULT NULL,
`username` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO `blog` VALUES ('5', '今天天气真好', '一起出去玩啊', 'fzp');
sys-log.sql
CREATE DATABASE `sys-log` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
use `sys-log`;
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for sys_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`create_date` datetime DEFAULT NULL,
`ip` varchar(255) DEFAULT NULL,
`method` varchar(255) DEFAULT NULL,
`operation` varchar(255) DEFAULT NULL,
`params` varchar(255) DEFAULT NULL,
`username` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=267 DEFAULT CHARSET=utf8;
sys-user.sql
CREATE DATABASE `sys-user` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
use `sys-user`;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`password` varchar(255) DEFAULT NULL,
`username` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`user_id` bigint(20) NOT NULL,
`role_id` bigint(20) NOT NULL,
KEY `FKa68196081fvovjhkek5m97n3y` (`role_id`),
KEY `FK859n2jvi8ivhui0rl0esws6o` (`user_id`),
CONSTRAINT `FK859n2jvi8ivhui0rl0esws6o` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
CONSTRAINT `FKa68196081fvovjhkek5m97n3y` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES ('1', '$2a$10$rlM./Q4dh5qXYmxFxUqkRetMPf6JewV/Hj/s4qBg/6U1.mzcue2oK', 'fzp');
INSERT INTO `role` VALUES ('1', 'ROLE_USER');
INSERT INTO `role` VALUES ('2', 'ROLE_ADMIN');
INSERT INTO `user_role` VALUES ('1', '1');
INSERT INTO `user_role` VALUES ('1', '2');
5. 测试
注册
http://localhost:5000/userapi/user/registry
post json请求
{
"password":"123456",
"username":"miya2"
}
返回:
{
"id": 15,
"username": "miya2",
"password": "$2a$10$I/mvNH15Axgv/M6e6ml8WuFt2kEjzY4K9PXNlFfDr.50FSq563Aca"
}
登录
http://localhost:5000/userapi/user/login
post请求, form-data请求,加两个参数:
username miya2
password 123456
返回:
{
"code": 0,
"error": "",
"data": {
"user": {
"id": 15,
"username": "miya2",
"password": "$2a$10$I/mvNH15Axgv/M6e6ml8WuFt2kEjzY4K9PXNlFfDr.50FSq563Aca"
},
"token": "eyJhbGciOiJSU9YQ4bKw"
}
}
获取用户的API
post
http://localhost:5000/userapi/user/miya
hearder
Authorization 值为:Bearer eyJhbGcixxxtoken
{
"error": "access_denied",
"error_description": "不允许访问"
}
- 在库里增加权限,id=15,权限为1
- 重新登录,用新的 token,再次访问,即可访问成功
{
"code": 0,
"error": "",
"data": {
"id": 13,
"username": "miya",
"password": "$2a$10$FM3gA3uFuYwDXH8BIfKm9egtWQRTDyM4z885rY5UMXnflcVVgbYie"
}
}
发布博客
http://localhost:5000/blogapi/blog post请求:
Authorization 注意header
{
"id":"20",
"username":"miya",
"title":"标题1",
"suject":"主题1"
}
返回这样的内容:
{
"code": 0,
"error": "",
"data": {
"id": 6,
"username": "miya",
"title": "标题1",
"suject": "主题1"
}
}
查看博客
http://localhost:5000/blogapi/blog/6/detail 获得博客。
上面的ID没用
{
"code": 0,
"error": "",
"data": {
"blog": {
"id": 6,
"username": "miya",
"title": "标题1",
"suject": "主题1"
},
"user": {
"id": 13,
"username": "miya",
"password": "$2a$10$FM3gA3uFuYwDXH8BIfKm9egtWQRTDyM4z885rY5UMXnflcVVgbYie"
}
}
其他测试
config-server 测试
- 引用了 starter-bus-amqp,用于事实更新,所以配置Mq
1. Queue springCloudBus.anonymous.Z8qPhVppRkuH0RA-v0jCXg
启动后,mq服务器上 有此队列,绑定到:springCloudBus
2. 访问:http://localhost:8769/uaa-service-pro.yml,即可得到此项目配置
访问:http://localhost:8769/admin-service-pro.yml 有一个默认 8761的 eureka配置,不知原因。共两个eureka配置
service-url:
defaultZone: http://localhost:8761/eureka/
3. 阿波罗会在本地这样的缓存,C:\opt\data\account-system\config-cache 。cloud config好像没有
jolokia
http://localhost:8769/actuator/jolokia
http://localhost:8769/actuator
{
"request": {
"type": "version"
},
"value": {
"agent": "1.6.0",
"protocol": "7.2",
"config": {
"listenForHttpService": "true",
"authIgnoreCerts": "false",
"agentId": "192.168.44.1-14152-4def42c3-servlet",
"debug": "false",
"agentType": "servlet",
"policyLocation": "classpath:/jolokia-access.xml",
},
"info": {
"product": "tomcat",
"vendor": "Apache",
"version": "9.0.12"
}
},
"timestamp": 1629773086,
"status": 200
}
zipkin
- 启动访问 http://localhost:9411/zipkin/
zk也是注册到 eureka 上的。哎,不能用。
项目整理
admin-server:9998
blog-service:8763
gateway-service:5000
logger-service:9997
monitor-service:8766
uaa-service:9999
user-service:8762
zipkin-server:9411
共计11个项目,配置中心,和 common 和 eureka 不算。还有8个。
另外 zipkin server需要 自己建立
user blog log都配置了 Mq,队列:spring-boot 交换器:spring-boot-exchange 。加上 配置中心,mq一共4个客户端。
监控相关admin和 dashboard
登录admin server:http://localhost:9998/#/applications
输入用户名密码:admin
看 web下 mappings, 可以看到所有的 请求路径 mappings
1. 访问:monitor-service 的 dashboard:
http://localhost:8766/hystrix
2. turbine.stream访问:
在dashboard填入:http://localhost:8766/turbine.stream 看配置 自动聚合了:blog-service,user-service 的 hystrix
http://localhost:8766/actuator/hystrix.stream 监控项目的hystrix无用的。
3. 访问下面的接口后:,dashboard里面将会有内容:
http://localhost:5000/userapi/user/miya
http://localhost:5000/blogapi/blog/miya2
(重要的是下面登录接口)
http://localhost:5000/blogapi/blog/6/detail 会访问:/user-service/user/{username}
http://localhost:5000/userapi/user/login?username=miya2&password=123456
//会访问:/uaa-service/oauth/token
访问成功后,即可看到 dashboard