Pull hook education PC Station - micro service version
- Lao sun
1. Review microservices
1. General
Let's first look at how micro services are described in Martin Fowler's paper
Microservice is an architecture mode or architecture style. It advocates dividing a single application into a group of small services. Each service runs in its own process - Services coordinate and configure with each other to provide final value to users;
Popular points:
In the feudal dynasty, there were many states and counties. Each state and county was a relative of the emperor. There were also generals who made outstanding contributions, Marquis of Zhennan, King Pingxi and other senior officials of the frontier. Each of them was a symbol of the highest power in their own jurisdiction.
Their own states and counties operate independently (single structure)
However, due to different local conditions and customs, the ruling strategy should also meet different needs. (the South has cultural heritage, and Confucius can educate it. The folk custom in the northwest is fierce, so thunder must be used)
The unification of different states and counties, large and small, is a Supreme Court (general structure)
Summary:
Split the traditional one-stop application into an independent service and completely decouple it. Each service provides a service with a single business function. One service does one thing, establishes an independent process, can start and destroy by itself, and even has an independent database.
2. Advantages
-
Each service is cohesive, small enough, simple to develop and efficient. One service does one thing;
-
Microservices are loosely coupled and independent in both development and deployment phases;
-
Microservices can be developed in different languages;
4) Microservices are just business logic code and will not be mixed with HTML, CSS or other page components;
- Each service has its own storage capacity. It can have its own database. Of course, it can also have unified data;
3. Disadvantages
-
Developers need to deal with the complexity of distributed systems;
-
With the increase of services, the difficulty of operation and maintenance becomes greater;
-
System deployment dependency
-
Increased communication costs
-
Data consistency is difficult
-
System integration test trouble
-
Performance monitoring is not easy
-
. . . .
4. Microservices and microservice architecture
4.1 microservices
-
It emphasizes the size of a service. * * focuses on a point * *, which can solve an existing application, similar to a project / module in the project;
-
Separate dental hospital, Eye Hospital;
-
Mobile phones, computers, sofas, mattresses and sportswear are all micro services;
-
**Focus on individuals, and each individual completes a specific task or function**
4.2 microservice architecture
-
An architecture model, which advocates that a single application is divided into a group of small services, which coordinate and cooperate with each other to provide users with ultimate value;
-
Lightweight communication mechanism is adopted between services (RESTfull of HTTP protocol)
-
Each service is built around specific business and can be independently deployed to the production environment;
-
Try to avoid a unified and centralized service management mechanism
-
Not a single clinic. All our clinics are integrated to form a comprehensive hospital
-
Xiaomi ecological chain, toothbrush, rice cooker, mobile phone and router are all Xiaomi's.
4.3 what is the difference between springcloud and SpringBoot?
- SpringBoot focuses on developing individual services quickly and easily;
- SpringCloud focuses on the coordination and arrangement of global microservices. It integrates individual microservices developed by SpringBoot;
- SpringBoot can be used and developed independently, but SpringCloud is inseparable from SpringBoot and belongs to dependency relationship;
- SpringBoot belongs to a department, and SpringCloud is a general hospital;
4.4 spring cloud vs. Dubbo
**Dubbo ** | **SpringCloud ** | |
---|---|---|
Service registry | Zookeeper | String Cloud Netflix Eureka |
Service invocation mode | RPC | REST API |
Service monitoring | Dubbo-monitor | Spring Boot Admin |
Circuit breaker | imperfect | Spring Cloud Netflix Hystrix |
Service gateway | nothing | Spring Cloud Netflix Zuul |
Distributed configuration | nothing | Spring Cloud Config |
Service tracking | nothing | Spring Cloud Sleuth |
Message bus | nothing | Spring Cloud Bus |
data stream | nothing | Spring Cloud Stream |
Batch task | nothing | Spring Cloud Task |
Brand machine and assembly machine
2. Microservice architecture project
-
Edu Lagou: parent project
-
Edu API: common sub module
-
Edu Eureka boot: Service Center: 7001
-
Edu user boot: user micro service: 8001
-
Edu course boot: course microservice: 8002
-
Edu order boot: order micro service: 8003
-
Edu pay boot: payment micro service: 8004
-
Edu comment boot: Message micro service: 8005
3. Construction project
3.1 parent project
3.2 create a service center
-
Create a new Module in the parent project
-
Open the service manager to make debugging easier
@SpringBootApplication @EnableEurekaServer //Open Eureka service public class EduEurekaBootApplication { public static void main(String[] args) { SpringApplication.run(EduEurekaBootApplication.class, args); } }
- Modify the suffix of the configuration file to yml
server: # Configure service port port: 7001 eureka: client: service-url: # Configure eureka server address defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #Whether you need to register yourself with the registry (the registry cluster needs to be set to true) register-with-eureka: false #Do you need to search service information? Because you are a registry, it is false fetch-registry: false
- Start project
3.3 creating micro services
@SpringBootApplication @EnableEurekaClient // Clients registered to the hub public class EduUserBootApplication { public static void main(String[] args) { SpringApplication.run(EduUserBootApplication.class, args); } }
- Modify configuration file suffix yml
server: # Service port number port: 8001 spring: application: # Service name - the name used to communicate between services name: edu-user-boot eureka: client: service-url: # Fill in the address of the registry server defaultZone: http://localhost:7001/eureka # Do you need to register yourself with the registry register-with-eureka: true # Need to search for service information fetch-registry: true instance: # Register with the registry using an ip address prefer-ip-address: true # Status parameters displayed in the registry list instance-id: ${spring.cloud.client.ip-address}:${server.port}
package com.lagou.eduuserboot.com.lagou.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @BelongsProject: edu-lagou * @Author: GuoAn.Sun * @CreateTime: 2020-10-15 15:54 * @Description: */ @RestController public class TestAction { @GetMapping("hello1") public String hello1(){ System.out.println("Hello, sun!"); return "Hi,Lao sun!"; } }
-
Start service
-
Final effect:
3.3.1 user micro service
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--pojo Persistent use--> <dependency> <groupId>javax.persistence</groupId> <artifactId>javax.persistence-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency>
- Configure database parameters
server: # Service port number port: 8001 spring: application: # Service name - the name used to communicate between services name: edu-user-boot datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.204.141:3306/edu?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username: root password: 123123
- Entity class
import lombok.Data; import javax.persistence.Id; import javax.persistence.Table; import java.io.Serializable; import java.util.Date; @Data @Table(name = "user") public class User implements Serializable { private static final long serialVersionUID = -89788707895046947L; /** * User id */ @Id private Integer id;
- mapper
package com.lagou.eduuserboot.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lagou.eduuserboot.entity.User; /** * @BelongsProject: edu-lagou * @Author: GuoAn.Sun * @CreateTime: 2020-10-15 17:38 * @Description: */ public interface UserMapper extends BaseMapper<User> { }
- Interface
public interface UserService { User getOne(Integer id); }
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public User getOne(Integer id) { return userMapper.selectById(id); } }
- Control layer
@RestController public class TestAction { @Autowired private UserService userService; @GetMapping("hello1/{id}") public User hello1(@PathVariable("id") Integer id){ System.out.println("Hello, sun!"); return userService.getOne(id); } }
- Startup class
@SpringBootApplication @EnableEurekaClient // Clients registered to the hub @MapperScan("com.lagou.eduuserboot.mapper") // Scan mapper package public class EduUserBootApplication { public static void main(String[] args) { SpringApplication.run(EduUserBootApplication.class, args); } }
- Transform with the original business
public interface UserService { /** * @param phone cell-phone number * @param password password * @return User object */ User login(String phone, String password); /**Check whether the mobile phone number has been registered * * @param phone cell-phone number * @return 0: Not registered, 1: registered */ Integer checkPhone(String phone); /** * User registration * * @param phone cell-phone number * @param password password * @param nickname nickname * @param headimg head portrait * @return Number of rows affected */ Integer register( String phone, String password,String nickname,String headimg); }
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public Integer checkPhone(String phone) { //Create condition constructor QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); // The first parameter is the name of the field in the database. Remember, it is the field in the database // The second parameter is the content to query userQueryWrapper.eq("phone",phone); // selectOne() query is one. If more than one query meets the criteria, an error will be reported User user = userMapper.selectOne(userQueryWrapper ); if(user == null){ return 0; } return 1; } public User login(String phone, String password) { QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.eq("phone",phone); userQueryWrapper.eq("password",password); User user = userMapper.selectOne(userQueryWrapper ); return user; } public Integer register(String phone, String password,String nickname,String headimg) { User user = new User(); user.setPhone(phone); user.setPassword(password); user.setName(nickname); user.setPortrait(headimg); int insert = userMapper.insert(user); return insert; } }
- Start vue project test login
@RestController @RequestMapping("user") @CrossOrigin // Cross domain public class UserController { }
3.3.2 course microservices
- Mybatis plus does not support complex multi table Association queries. If you encounter complex multi table queries, you can still use the mybatis+xml configuration file. The usage is the same as before
- Modify the yml configuration file and tell the program where to find mapper xml
mybatis-plus: mapper-locations: classpath:mybatis/mapper/*.xml #Create mybatis/mapper under resources
- Put the previously written coursedao Copy the XML and modify it:
<!--Modify package path: mapper Complete interface path--> <mapper namespace="com.lagou.educourseboot.mapper.CourseMapper"> <resultMap type="com.lagou.educourseboot.entity.Course" id="CourseMap"> ...ellipsis
- Add our custom methods to the mapper interface
public interface CourseMapper extends BaseMapper<Course> { /** * Query all course information * @return */ List<Course> getAllCourse(); /** * Query all course information purchased by logged in users * @return */ List<Course> getCourseByUserId(@Param("userId") String userId); /** * Query the details of a course * @param courseid Course number * @return */ Course getCourseById(@Param("courseid") Integer courseid); }
- service
public interface CourseService { /** * Query all course information * @return */ List<Course> getAllCourse(); /** * Query all course information purchased by logged in users * @return */ List<Course> getCourseByUserId(String userId); /** * Query the details of a course * @param courseid Course number * @return */ Course getCourseById(Integer courseid); }
- controller
@RestController @RequestMapping("course") @CrossOrigin //Cross domain public class CourseController { @Autowired private CourseService courseService; @GetMapping("getAllCourse") public List<Course> getAllCourse() { List<Course> list = courseService.getAllCourse(); return list; } @GetMapping("getCourseByUserId/{userid}") public List<Course> getCourseByUserId( @PathVariable("userid") String userid ) { List<Course> list = courseService.getCourseByUserId(userid); return list; } @GetMapping("getCourseById/{courseid}") public Course getCourseById(@PathVariable("courseid")Integer courseid) { Course course = courseService.getCourseById(courseid); return course; } }
3.3.3 message micro service
- yml
datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.204.141:3306/edu?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username: root password: 123123
- serviceImpl implementation
public interface CommentService { /** * Save message * @param comment Message content object * @return Number of rows affected */ Integer saveComment(CourseComment comment); /** * All messages of a course (pagination) * @param courseid Course number * @param offset Data offset * @param pageSize Entries per page * @return Message collection */ List<CourseComment> getCommentsByCourseId(Integer courseid, Integer offset, Integer pageSize); /** * give the thumbs-up * @param comment_id Message number * @param userid User number * @return 0: Saving failed, 1: saving succeeded */ Integer saveFavorite(Integer comment_id,Integer userid); /** * Cancel like * @param comment_id Message number * @param userid User number * @return 0: Saving failed, 1: saving succeeded */ Integer cancelFavorite(Integer comment_id,Integer userid); }
@Service public class CommentServiceImpl implements CommentService { @Autowired private CourseCommentDao courseCommentDao; @Autowired private CourseCommentFavoriteRecordDao courseCommentFavoriteRecordDao; //Save message public Integer saveComment(CourseComment comment) { return courseCommentDao.insert(comment); } public List<CourseComment> getCommentsByCourseId(Integer courseid, Integer offset, Integer pageSize) { return courseCommentDao.getCommentsByCourseId(courseid, offset, pageSize); } /** *give the thumbs-up: * First check whether the current user likes this message, * If you click too: modify is_del status, cancel like * If not: save a like message * * Finally, update the number of likes */ public Integer saveFavorite(Integer comment_id, Integer userid) { QueryWrapper<CourseCommentFavoriteRecord> q1 = new QueryWrapper(); q1.eq("comment_id", comment_id); q1.eq("user_id", userid); Integer i = courseCommentFavoriteRecordDao.selectCount(q1); int i1 = 0; int i2 = 0; if(i == 0){ //I didn't like it //Save like CourseCommentFavoriteRecord favoriteRecord = new CourseCommentFavoriteRecord(); favoriteRecord.setCommentId(comment_id); favoriteRecord.setUserId(userid); favoriteRecord.setIsDel(0); favoriteRecord.setCreateTime(new Date()); favoriteRecord.setUpdateTime(new Date()); i1 = courseCommentFavoriteRecordDao.insert(favoriteRecord); }else{ //Change the like status of this message to 0, indicating that the like has been cancelled CourseCommentFavoriteRecord favoriteRecord = new CourseCommentFavoriteRecord(); favoriteRecord.setIsDel(0); QueryWrapper<CourseCommentFavoriteRecord> q2 = new QueryWrapper(); q2.eq("comment_id", comment_id); q2.eq("user_id", userid); i1 = courseCommentFavoriteRecordDao.update(favoriteRecord,q2); } i2 = courseCommentDao.updateLikeCount(1,comment_id); if(i1==0 || i2==0){ throw new RuntimeException("Like failed!"); } return comment_id; } /** * Delete likes (is_del = 1) * Update the number of comment likes - 1 * @param comment_id Message number * @param userid User number * @return 0:Failure, 1: success */ public Integer cancelFavorite(Integer comment_id, Integer userid) { CourseCommentFavoriteRecord favoriteRecord = new CourseCommentFavoriteRecord(); favoriteRecord.setIsDel(1); QueryWrapper<CourseCommentFavoriteRecord> q1 = new QueryWrapper(); q1.eq("comment_id", comment_id); q1.eq("user_id", userid); Integer i1 = courseCommentFavoriteRecordDao.update(favoriteRecord,q1); Integer i2 = courseCommentDao.updateLikeCount(-1,comment_id); if(i1==0 || i2==0){ throw new RuntimeException("Failed to cancel like!"); } return i2; } }
- mapper
@Service public interface CourseCommentDao extends BaseMapper<CourseComment> { /** * All messages of a course (pagination) * @param courseid Course number * @param offset Data offset * @param pageSize Entries per page * @return Message collection */ @Select({"select\n" + " cc.id cc_id,`course_id`,`section_id`,`lesson_id`,cc.user_id cc_user_id,`user_name`,`parent_id`,`is_top`,`comment`,`like_count`,`is_reply`,`type`,`status`,cc.create_time cc_create_time ,cc.update_time cc_update_time ,cc.is_del cc_is_del,`last_operator`,`is_notify`,`mark_belong`,`replied` ,\n" + " ccfr.id ccfr_id,ccfr.user_id ccfr_user_id,comment_id,ccfr.is_del ccfr_is_del,ccfr.create_time ccfr_create_time,ccfr.update_time ccfr_update_time\n" + " from course_comment cc left join (select * from course_comment_favorite_record where is_del = 0) ccfr\n" + " on cc.id = ccfr.comment_id\n" + " where cc.is_del = 0\n" + " and course_id = #{courseid}\n" + " order by is_top desc , like_count desc , cc.create_time desc\n" + " limit #{offset}, #{pageSize}"}) @Results({ @Result(column = "cc_id",property = "id"), @Result(column = "cc_user_id",property = "userId"), @Result(column = "cc_create_time",property = "createTime"), @Result(column = "cc_update_time",property = "updateTime"), @Result(column = "cc_is_del",property = "isDel"), @Result(column = "comment_id", property = "favoriteRecords",many = @Many(select = "com.lagou.educommentboot.mapper.CourseCommentFavoriteRecordDao.findByCommentid")) }) List<CourseComment> getCommentsByCourseId(@Param("courseid") Integer courseid, @Param("offset") Integer offset, @Param("pageSize") Integer pageSize); /** * Update the number of likes * @param x +1 If yes, the number of likes increases, and if - 1, the number of likes decreases * @param comment_id Number of a message * @return 0: Saving failed, 1: saving succeeded */ @Update({"update course_comment set like_count = like_count + #{x} where id = #{comment_id}"}) Integer updateLikeCount(@Param("x") Integer x, @Param("comment_id") Integer comment_id); }
public interface CourseCommentFavoriteRecordDao extends BaseMapper<CourseCommentFavoriteRecord> { @Select({"select * from course_comment_favorite_record where comment_id = #{comment_id}"}) List<CourseCommentFavoriteRecord> findByCommentid(Integer comment_id); }
- Startup class
@SpringBootApplication @EnableEurekaClient // Clients registered to the hub @MapperScan("com.lagou.educommentboot.mapper") public class EduCommentBootApplication { public static void main(String[] args) { SpringApplication.run(EduCommentBootApplication.class, args); } }
http://localhost:8005/course/comment/getCourseCommentList/7/1/20
3.3.4 payment micro service
- Compared with the previous, there is no change
<dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency> <dependency> <groupId>com.jfinal</groupId> <artifactId>jfinal</artifactId> <version>3.5</version> </dependency>
@RestController @RequestMapping("order") @CrossOrigin public class WxPayController { @GetMapping("createCode") public Object createCode(String courseid,String coursename,String price) throws Exception { coursename = new String(coursename.getBytes("ISO-8859-1"),"UTF-8"); Map<String,String> mm = new HashMap(); mm.put("appid", PayConfig.appid); // Public account ID mm.put("mch_id",PayConfig.partner);// Merchant number mm.put("nonce_str", WXPayUtil.generateNonceStr());//Random string mm.put("body",coursename); //Brief description of goods mm.put("out_trade_no",WXPayUtil.generateNonceStr()); //Randomly generated merchant order number mm.put("total_fee",price); // Order amount, total order amount, in minutes, can only be an integer mm.put("spbill_create_ip","127.0.0.1"); // Terminal IP mm.put("notify_url",PayConfig.notifyurl); //Notification address mm.put("trade_type","NATIVE"); //Transaction type //System.out.println("merchant information:" + mm); //2. Generate digital signature and convert merchant information into xml format String xml = WXPayUtil.generateSignedXml(mm, PayConfig.partnerKey); //System.out.println("merchant's xml information:" + xml); //3. Send xml data to wechat payment platform to generate orders String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; // Send the request and return a string in xml format String result = HttpKit.post(url, xml); //4. Wechat payment platform returns xml format data, converts it into map format and returns it to the front end Map<String, String> resultMap = WXPayUtil.xmlToMap(result); resultMap.put("orderId",mm.get("out_trade_no")); return resultMap; } @GetMapping("checkOrderStatus") public Object checkOrderStatus(String orderId) throws Exception { //1. Prepare merchant information Map<String,String> mm = new HashMap(); mm.put("appid", PayConfig.appid); // Public account ID mm.put("mch_id",PayConfig.partner);// Merchant number mm.put("out_trade_no", orderId);//Merchant order number mm.put("nonce_str",WXPayUtil.generateNonceStr()); //Random string //2. Generate digital signature String xml = WXPayUtil.generateSignedXml(mm, PayConfig.partnerKey); //3. Send query request to wechat payment platform String url = "https://api.mch.weixin.qq.com/pay/orderquery"; // Start time of querying order status long beginTime = System.currentTimeMillis(); // Keep going to the wechat payment platform to ask whether the payment is successful while(true) { //4. Process the query results returned by wechat payment platform String result = HttpKit.post(url, xml); Map<String, String> resultMap = WXPayUtil.xmlToMap(result); //Payment has been made successfully. Don't ask again if(resultMap.get("trade_state").equalsIgnoreCase("SUCCESS")){ return resultMap; } //If you fail to pay for more than 30 seconds, stop asking if( System.currentTimeMillis()- beginTime > 30000 ){ return resultMap; } Thread.sleep(3000); //Ask the wechat payment platform every 3 seconds } } }
3.3.5 order micro service
-
Change the @ Reference in the controller to @ Autowired
-
dao interface
@Service public interface OrderDao extends BaseMapper<UserCourseOrder> { }
- service implementation class (mybatis plus transformation)
@Service public class OrderServiceImpl implements OrderService { @Autowired private OrderDao orderDao; // Save order public void saveOrder(String orderNo, String user_id, String course_id, String activity_course_id, String source_type) { UserCourseOrder order = new UserCourseOrder(); order.setOrderNo(orderNo); order.setUserId(user_id); order.setCourseId(course_id); order.setActivityCourseId( Integer.parseInt(activity_course_id)); order.setSourceType(source_type); order.setIsDel(0); order.setStatus(0); order.setCreateTime(new Date()); order.setUpdateTime(new Date()); orderDao.insert(order); } // Update order status public Integer updateOrder(String orderNo, int status) { UserCourseOrder order = new UserCourseOrder(); order.setStatus(status); QueryWrapper<UserCourseOrder> qw = new QueryWrapper(); qw.eq("order_no", orderNo); return orderDao.update(order,qw); } // Delete order public Integer deleteOrder(String orderNo) { QueryWrapper<UserCourseOrder> qw = new QueryWrapper(); qw.eq("order_no", orderNo); // delete from user_course_order where order_no = orderNo return orderDao.delete(qw); } // Query all orders of a user public List<UserCourseOrder> getOrdersByUserId(String userId) { QueryWrapper<UserCourseOrder> qw = new QueryWrapper(); qw.eq("user_id", userId); //select * from user_course_order where user_id = userId return orderDao.selectList(qw); } }
3.4 sub warehouse reconstruction
-
As we said before, microservices can have their own independent database. Now we use sub database to realize it
-
Re plan the ownership of databases and tables
- User micro service: edu_user library - > User
- Course micro service: edu_course library - > course, course_section,activity_course,course_lesson,course_media,teacher
- Message micro service: edu_comment Library - > Course_ comment,course_comment_favorite_record
- Order micro service: edu_order Library - > 10 tables from order: user_course_order_0,user_course_order_1,user_course_order_2 …
-
Modify the database parameters in the yml configuration of each microservice
-
The transformation of courses and orders after sub warehouse is as follows:
- Through the logged in user id, go to the order micro service to query the course id that the user has successfully purchased
-
Send all the purchased course IDs to the course micro service to query the course details
Use axios to send a cross domain request, followed by the parameters, which cannot be in the format of array!
For example: xxx?ids=[1,2,3], this format is not allowed. It should be changed to XXX? 0 = 1 & 1 = 2 & 2 = 3 (012 is the subscript of the array)
Solution: use qs object instead of JSON object for conversion
data() { return { myCourseIds:[], // List of courses I have purchased }; }, created() { this.getCourseList(); //When the component is created, the method of obtaining all courses will be called this.user = JSON.parse( localStorage.getItem("user") ); if( this.user != null ){ this.isLogin = true; //Logged in this.getMyCourseIds(); //1. Get the course number of the current user who has purchased (status=20) } }, getMyCourseList(){ //2. Query course information according to course number return this.axios .get("http://localhost:8002/course/getCourseByCourseId",{ Headers:{ 'Content-Type':'text/plain' }, params:{ ids:qs.stringify(this.myCourseIds); // npm install qs, import qs from 'qs' is introduced on this page; } }).then((result) => { console.log(result); this.myCourseList = result.data; }).catch( (error)=>{ this.$message.error("Failed to obtain the purchased course information!"); }); }, getMyCourseIds(){ return this.axios .get("http://localhost:8003/order/getOKOrderIdsByUserId/"+this.user.content.id) .then((result) => { this.myCourseIds = result.data; console.log("curriculum id: "+this.myCourseIds); this.getMyCourseList();// To get the purchased courses }).catch( (error)=>{ this.$message.error("Get purchased courses ID Failed!"); }); }
- Order micro service
// Query the course ID that the user has purchased @GetMapping("getOKOrderIdsByUserId/{userid}") public List<Object> getOKOrderIdsByUserId(@PathVariable("userid")String userid ) { System.out.println("Whose course number to query:"+userid); List<UserCourseOrder> list = orderService.getOKOrderIdsByUserId(userid); List<Object> list2 = new ArrayList<>(); for(UserCourseOrder order : list){ list2.add(order.getCourseId()); } System.out.println(list2); return list2; }
List<UserCourseOrder> getOKOrderIdsByUserId(String userId);
public List<UserCourseOrder> getOKOrderIdsByUserId(String userId) { QueryWrapper<UserCourseOrder> qw = new QueryWrapper(); qw.select("course_id"); // Query the specified column: select count_id from table qw.eq("user_id", userId); qw.eq("status", 20); return orderDao.selectList(qw); }
- Course micro service
// Passed on successfully purchased course ID (multiple courses) @GetMapping("getCourseByCourseId") public List<Course> getCourseByCourseId( String ids ) { System.out.println(ids); // The format is id = 1 & id = 2 & id = 3 List<String> idList = new ArrayList<>(); // Extract all id values in the string while(ids.indexOf("=")>0){ String id; if(ids.indexOf("&")>0){ id = ids.substring(ids.indexOf("=")+1,ids.indexOf("&")); System.out.println(id); ids = ids.substring(ids.indexOf("&")+1); }else{ id = ids.substring(ids.indexOf("=")+1); System.out.println(id); ids = ids.substring(ids.indexOf("=")+1); } idList.add(id); } System.out.println(idList); List<Course> list = courseService.getCourseByCourseId(idList); return list; }
/** * Query all course information purchased by logged in users * @return */ List<Course> getCourseByCourseId(List<String> idList);
@Override public List<Course> getCourseByCourseId(List<String> idList) { return courseMapper.getCourseByCourseId(idList); }
/** * Query all course information purchased by logged in users * @return */ List<Course> getCourseByCourseId(@Param("idList") List<String> idList);
<select id="getCourseByCourseId" resultMap="CourseMap"> <include refid="courseInfo"/> where c.id in <foreach collection="idList" open="(" separator="," close=")" item="cid"> #{cid} </foreach> order by amount desc , c_id , ac_create_time desc </select>
3.5 application of order sub table
- Sharding JDBC provides services in the form of jar package, so maven dependency should be introduced first
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.0.0-RC1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency>
- Create sub database and sub table configuration class
package com.example.demo.config; import com.alibaba.druid.pool.DruidDataSource; import org.apache.shardingsphere.api.config.sharding.KeyGeneratorConfiguration; import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration; import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration; import org.apache.shardingsphere.api.config.sharding.strategy.InlineShardingStrategyConfiguration; import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * @BelongsProject: demo * @Author: GuoAn.Sun * @Description: Configure fragmentation rules */ @Configuration public class ShardingJdbcConfig { // Define data source Map<String, DataSource> createDataSourceMap() { DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setUrl("jdbc:mysql://192.168.204.141:3306/edu_order?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"); ds.setUsername("root"); ds.setPassword("123123"); Map<String, DataSource> result = new HashMap<>(); result.put("m1", ds); return result; } // Define primary key generation policy private static KeyGeneratorConfiguration getKeyGeneratorConfiguration() { KeyGeneratorConfiguration result = new KeyGeneratorConfiguration("SNOWFLAKE", "id"); return result; } // Definition t_ Sharding strategy of order table TableRuleConfiguration getOrderTableRuleConfiguration() { TableRuleConfiguration result = new TableRuleConfiguration("user_course_order", "m1.user_course_order_$->{0..9}"); result.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("id", "user_course_order_$->{id % 2 + 1}")); result.setKeyGeneratorConfig(getKeyGeneratorConfiguration()); return result; } // Defining sharding JDBC data sources @Bean DataSource getShardingDataSource() throws SQLException { ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration()); //spring.shardingsphere.props.sql.show = true Properties properties = new Properties(); properties.put("sql.show", "true"); return ShardingDataSourceFactory.createDataSource(createDataSourceMap(), shardingRuleConfig, properties); } }
- yml
server: port: 8003 spring: application: name: edu-order-boot eureka: client: service-url: defaultZone: http://localhost:7001/eureka register-with-eureka: true fetch-registry: true instance: prefer-ip-address: true instance-id: ${spring.cloud.client.ip-address}:${server.port}
- Startup class
import org.apache.shardingsphere.shardingjdbc.spring.boot.SpringBootConfiguration; @SpringBootApplication(exclude = {SpringBootConfiguration.class}) // Shield the use of spring in the startup class Class of shardingsphere configuration item @EnableEurekaClient // Clients registered to the hub @MapperScan("com.lagou.eduorderboot.mapper") public class EduOrderBootApplication { public static void main(String[] args) { SpringApplication.run(EduOrderBootApplication.class, args); } }
3.6 modification of playback components
-
The < video > component tag of html we used in the front-end project can be played. Although it can be played, it has a single function and can only be played, which can not ensure the security of video resources
-
We adopt Alibaba cloud video on demand
-
Alibaba official website: https://www.alibabacloud.com/help/zh/doc-detail/51236.htm?spm=a2c63.p38356.b99.2.28213799QTbeE3
-
High end extra services are charged according to traffic
-
Transform the front-end project and add Alibaba playback components
- Index. In the public directory of vue project Introducing css and js into HTML files
<link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.8.2/skins/default/aliplayer-min.css" /> <script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.8.2/aliplayer-min.js"></script>
- Introduce in your own vue component
<div class="prism-player" id="J_prismPlayer" ></div>
- vue binding
var player = new Aliplayer({ id: 'J_prismPlayer', width: '100%', height: '900px', autoplay: true, //This playback address supports the highest playback priority source : 'https://video.pearvideo.com/mp4/short/20200914/cont-1697119-15382138-hd.mp4', });
3.6.1 switching video using player
<template> <div> <div id="video-box"> <div class="prism-player" id="J_prismPlayer" ></div> </div> <button @click="playLesson('https://xxxx. Mp4 ') "> Video 1 < / button > <button @click="playLesson('https://yyyy. Mp4 ') "> Video 2 < / button > </div> </template> <script> mounted(){ this.player = new Aliplayer({ id: 'J_prismPlayer', width: '100%', height: '900px', autoplay: true, //Support playback address playback, which has the highest playback priority source : '', }); }, methods:{ playLesson(mp4_url){ // Recreate the player every time (otherwise the video will be superimposed) document.getElementById("J_prismPlayer").remove(); var pdiv = document.createElement("div"); pdiv.setAttribute("class","prism-player"); pdiv.setAttribute("id","J_prismPlayer"); document.getElementById("video-box").appendChild(pdiv); this.player = new Aliplayer({ id: 'J_prismPlayer', width: '100%', height: '900px', autoplay: true, //Support playback address playback, which has the highest playback priority source : mp4_url, }); } } </script>
3.7 gateway
edu-gateway-boot
<!--GateWay gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--introduce webflux--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
@SpringBootApplication @EnableEurekaClient public class LagouCloudGatewayApplication { public static void main(String[] args) { SpringApplication.run(LagouCloudGatewayApplication.class, args); } }
server: port: 9000 eureka: client: service-url: defaultZone: http://localhost:7001/eureka register-with-eureka: true fetch-registry: true instance: prefer-ip-address: true instance-id: ${spring.cloud.client.ip-address}:${server.port} spring: application: name: edu-gateway-boot cloud: gateway: routes: - id: edu-routes-course # Route name uri: lb://Edu course boot # go to the registry to find the name of the micro service predicates: # When the assertion is successful, forwarding is used when it is handed over to a microservice for processing - Path=/course/** filters: - StripPrefix=1 # Remove the first part of the uri - id: edu-routes-comment uri: lb://edu-comment-boot predicates: - Path=/comment/** filters: - StripPrefix=1 - id: edu-routes-order uri: lb://edu-order-boot predicates: - Path=/order/** filters: - StripPrefix=1 - id: edu-routes-pay uri: lb://edu-pay-boot predicates: - Path=/pay/** filters: - StripPrefix=1 - id: edu-routes-user uri: lb://edu-user-boot predicates: - Path=/user/** filters: - StripPrefix=1
Test 1: http://localhost:9000/course/course/getAllCourse
Test 2: http://localhost:9000/comment/course/comment/getCourseCommentList/9/1/20
3.8 high concurrency redis to help you carry
- Introduce dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
- Modify yml
server: port: 8002 spring: application: name: edu-course-boot datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.204.141:3306/edu_course?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username: root password: 123123 redis: host: 192.168.204.141 port: 6379
- redis operation should be placed in controller? Or service?
@Service public class CourseServiceImpl implements CourseService { @Autowired private CourseMapper courseMapper; @Autowired private RedisTemplate<Object,Object> redisTemplate; @Override public List<Course> getAllCourse() { //Rename the serialized collection name in redis memory with String (increase readability) RedisSerializer rs = new StringRedisSerializer(); redisTemplate.setKeySerializer(rs); System.out.println("query redis"); List<Course> list = (List<Course>)redisTemplate.opsForValue().get("allCourses"); if(list == null){ //Go to database System.out.println("====MySql database===="); list = courseMapper.getAllCourse(); // Put the collection queried from the database into redis memory (key,value, expired seconds, tool class of seconds) redisTemplate.opsForValue().set("allCourses", list,10, TimeUnit.SECONDS); } return list; }
-
If an error occurs:
-
redis is currently read-only and cannot write data, because the current identity role is to become the master for the reason of
3.8.1 cache penetration of high parallel delivery
-
Because, let's assume that 1000 people enter the method execution at the same time, and 1000 people find the set from the cache, but they don't find it. Then, if we enter the next step, 1000 people will query the database at the same time. In this way, we query the database 1000 times, which is inefficient, and redis is not used. The reason for this is that after the first redis cache query, subsequent queries are not blocked, which is "cache penetration"
-
Simulate 20 threads with high concurrency
@GetMapping("getAllCourse") public List<Course> getAllCourse() { // Simulate multithreading: create a thread pool with a capacity of 20 ExecutorService es = Executors.newFixedThreadPool(20); // Simulate 20 threads to query at the same time for (int i = 1; i <= 20; i++) { es.submit(new Runnable() { @Override public void run() { courseService.getAllCourse(); } }); } return courseService.getAllCourse(); }
-
Solution:
-
1. The simplest and crudest solution -- synchronization method lock
public synchronized List<Course> getAllCourse() { xxxx }
-
2. A slightly more efficient scheme -- synchronous code block (double-layer detection lock)
@Autowired private RedisTemplate<Object,Object> redisTemplate; @Override public List<Course> getAllCourse() { RedisSerializer rs = new StringRedisSerializer(); redisTemplate.setKeySerializer(rs); System.out.println("query redis"); List<Course> list = (List<Course>)redisTemplate.opsForValue().get("allCourses"); if(list == null){ //Queue up, let the first person in and go through the process (the people behind will go through the cache) synchronized (this){ list = (List<Course>)redisTemplate.opsForValue().get("allCourses"); if(list == null){ //Go to database System.out.println("====MySql database===="); list = courseMapper.getAllCourse(); // Put the set queried from the database into redis memory redisTemplate.opsForValue().set("allCourses", list,10, TimeUnit.SECONDS); } } } return list; }
3.8.2 how to ensure that the data in redis is up-to-date
- If the course content changes, we will delete the relevant sets in redis when modifying the course content.
- Then save the latest data to the database
- When querying data, because the data in redis has been deleted, it will query the database at the first time to ensure that the data is up-to-date.
4. IDEA integration Docker deployment microservices
4.1 review docker
-
I want to build a house, so I move bricks, cut wood, draw drawings, and cement. After a fierce operation, the house was finally built.
-
After living for some time, I wanted to move back to my hometown in the northeast on a whim. At this time, according to the previous method, I can only go back to the northeast and move bricks, cut wood, draw drawings, cement and build a house again.
-
Suddenly, a fairy sister came and taught me a spell. This spell can make a copy of my house into a "mirror image" and put it in a treasure chest
-
Holding the treasure chest, I went back to the northeast. I used this "mirror image" to copy a house, reproduce it perfectly, and check in with my bag
-
Isn't it amazing? Corresponding to our project, the house is the project itself, the image is the copy of the project, and the treasure chest is the image warehouse
-
If you want to dynamically expand the capacity, take the project image from the warehouse and copy it casually
-
There is no need to pay attention to issues such as version, compatibility and deployment, which completely solves the embarrassment of "perfect development, online collapse, and non-stop troubleshooting environment"
4.2 installing docker
# Install docker on 192.168.204.141 [root@A ~]# yum -y install docker # Start docker [root@A ~]# systemctl start docker # View the running status of docker [root@A ~]# systemctl status docker
4.3 enable remote access
- Docker does not allow remote access by default
# Modify profile [root@A ~]# vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock
Version 19.03.5
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:5678 -H unix://var/run/docker.sock -H fd:// --containerd=/run/containerd/containerd.sock --graph /data/docker
Docker common ports
2375 port: unencrypted docker socket; Remote login root account without password to access the host (not enabled by default)
Port 2376: TLS encrypted socket. It is likely that the CI 4243 port of the server is modified as https 443 port
Port 2377: cluster mode socket, applicable to cluster manager, not docker client
Port 5000: docker registration service
4789 and 7946 ports: overlay network
# Reload profile [root@A ~]# systemctl daemon-reload # Restart docker [root@A ~]# service docker restart # Check whether the port is on [root@A ~]# netstat -nlpt # Verify that the port is valid [root@A ~]# curl http://113.31.144.141:2375/info [root@A ~]# curl http://106.75.253.40:2375/info
4.4 IDEA integration plug-in
- Search for Docker in Plugins and install it
https://owi3yzzk.mirror.aliyuncs.com
4.5 Docker Maven plug-in
- In the traditional process, we have to go through the steps of packaging, deployment, uploading to linux, writing Dockerfile, building image, creating container and so on
- Docker made plugin is to help us automatically generate images and push them to the warehouse during development
<plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.0.0</version> <configuration> <!--Image name laosun/test-docker-demo--> <imageName>laosun/${project.artifactId}</imageName> <!--Label version--> <imageTags> <imageTag>latest</imageTag> </imageTags> <!--Basic image, equivalent to Dockerfile Inside from--> <baseImage>java</baseImage> <!--Label version--> <maintainer>laosun angiersun@lagou.com</maintainer> <!--Entry point, project.build.finalName namely project Under label build Under label filename Label content, test-docker-demo--> <!--It is equivalent to automatic execution after starting the container java -jar/test-docker-demo.jar--> <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint> <!--docker address--> <dockerHost>http://192.168.204.141:2375</dockerHost> <!-- Here is copy jar Package to docker Container specified directory configuration --> <resources> <resource> <targetPath>/</targetPath> <!--The root directory of the copy, target--> <directory>${project.build.directory}</directory> <!--Which file to upload to docker,amount to Dockerfile Inside add test-docker-demo.jar /--> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin>
4.6 execute command
- Package the project and build the image to docker
- For the first time, wait a little longer to pull the java environment and other operations
mvn clean package docker:build
-
Tips
Run Commands using IDE Press Ctrl+Enter to run the highlighted action using the relevant IDE feature instead of the terminal. Press Ctrl+Shift+Enter for debug. Press Enter to run the command in the terminal as usual. You can turn this behavior on/off in Settings | Tools | Terminal. Got it! -
After the command is executed, the jar package image will be automatically pushed to docker
-
In the docker interface of idea, you can create a container according to the image.
-
"One mirror can create N containers"
-
If the following error occurs, please execute the corresponding command, which is the reason why the executable file cannot be found
[root@A ~]# cd /usr/libexec/docker/ [root@A docker]# ln -s docker-proxy-current docker-proxy
- The command that docker is running cannot be found
[root@A docker]# cd /usr/libexec/docker/ [root@A docker]# ln -s docker-runc-current docker-runc
-
visit: http://192.168.204.141:9001
-
error
The reason why docker+tomcat starts very slowly is JRE /dev/random blocking
1 enter the container
2 vim /etc/java-8-openjdk/security/java.security
3 find securerandom source=file:/dev/random
4. Change to securerandom source=file:/dev/./ urandom
5 restart the container -
docker container cross host access
- Download and install weave
[root@localhost node1]# curl -L git.io/weave -o /usr/bin/weave [root@localhost node1]# chmod a+x /usr/bin/weave [root@localhost node1]# weave version weave script 2.3.0 weave 2.3.0
- Start weave
after running, a running container, two data only containers and three images will be generated. Among them, these data of weave are saved on the container named weavedb allocated on each machine. It is a data volume container, which is only responsible for data persistence
a virtual network device named weave will also be generated
a custom network using weave bridge will also be generated in docker.
[root@node1 ~]# weave launch [root@node1 ~]# docker images |grep weave [root@node1 ~]# ifconfig weave [root@node1 ~]# docker network ls
- First use docker run to start the container, and then use the weave attach command to bind the IP address to the container
[root@linux-node1 ~]# weave attach 111.111.1.1/24 weave-test1 #Bind the weave-test1 container with an ip of 111.111.1.1 111.111.1.1 ## Enter container [root@linux-node1 ~]# docker exec -it weave-test1 /bin/bash [root@be8cc008d9f1 /]# ifconfig ##yum install -y net-tools
[root@linux-node2 ~]# weave attach 111.111.1.2/24 weave-test2 #Bind the weave-test2 container with an ip of 111.111.1.2 111.111.1.2 ## Enter container [root@linux-node2 ~]# docker exec -it weave-test2 /bin/bash [root@be8cc008d9f2 /]# ifconfig ##yum install -y net-tools
## Container interconnection ## By default, the above two containers created on node1 and node2 hosts cannot ping each other. ## You need to use the weave connect command to establish a connection between two weave routers. [root@linux-node1 ~]# weave connect 118.190.201.12 ##The ip of the host is connected. Note that "weave forget ip" z means to disconnect the connection [root@linux-node2 ~]# weave connect 118.190.201.11 ## Then enter the container for mutual ping [root@be8cc008d9f1 /]# ping 111.111.1.2 [root@be8cc008d9f2 /]# ping 111.111.1.1