基于redis的小程序登錄實(shí)現(xiàn)方法流程分析
這張圖是小程序的登錄流程解析:
小程序登陸授權(quán)流程:
在小程序端調(diào)用wx.login()獲取code,由于我是做后端開(kāi)發(fā)的這邊不做贅述,直接貼上代碼了.有興趣的直接去官方文檔看下,鏈接放這里: wx.login()
wx.login({ success (res) { if (res.code) { //發(fā)起網(wǎng)絡(luò)請(qǐng)求 wx.request({ url: 'https://test.com/onLogin', data: { code: res.code } }) } else { console.log('登錄失?。? + res.errMsg) } } })
小程序前端登錄后會(huì)獲取code,調(diào)用自己的開(kāi)發(fā)者服務(wù)接口,調(diào)用個(gè)get請(qǐng)求:
需要得四個(gè)參數(shù):
appid:小程序appid
secret: 小程序密鑰
js_code: 剛才獲取的code
grant_type:‘a(chǎn)uthorization_code' //這個(gè)是固定的
如果不出意外的話(huà),微信接口服務(wù)器會(huì)返回四個(gè)參數(shù):
詳情可以看下官方文檔:jscode2session
下面附上我的代碼:
@AuthIgnore @RequestMapping("/login") @ResponseBody public ResponseBean openId(@RequestParam(value = "code", required = true) String code, @RequestParam(value = "avatarUrl") String avatarUrl, @RequestParam(value = "city") String city, @RequestParam(value = "country") String country, @RequestParam(value = "gender") String gender, @RequestParam(value = "language") String language, @RequestParam(value = "nickName") String nickName, @RequestParam(value = "province") String province, HttpServletRequest request) { // 小程序端獲取的CODE ResponseBean responseBean = new ResponseBean(); try { boolean check = (StringUtils.isEmpty(code)) ? true : false; if (check) { responseBean = new ResponseBean(false, UnicomResponseEnums.NO_CODE); return responseBean; } //將獲取的用戶(hù)數(shù)據(jù)存入數(shù)據(jù)庫(kù); Map<String, Object> msgs = new HashMap<>(); msgs.put("appid", appId); msgs.put("secret", secret); msgs.put("js_code", code); msgs.put("grant_type", "authorization_code"); // java的網(wǎng)絡(luò)請(qǐng)求,返回字符串 String data = HttpUtils.get(msgs, Constants.JSCODE2SESSION); logger.info("======> " + data); String openId = JSONObject.parseObject(data).getString("openid"); String session_key = JSONObject.parseObject(data).getString("session_key"); String unionid = JSONObject.parseObject(data).getString("unionid"); String errcode = JSONObject.parseObject(data).getString("errcode"); String errmsg = JSONObject.parseObject(data).getString("errmsg"); JSONObject json = new JSONObject(); int userId = -1; if (!StringUtils.isBlank(openId)) { Users user = userService.selectUserByOpenId(openId); if (user == null) { //新建一個(gè)用戶(hù)信息 Users newUser = new Users(); newUser.setOpenid(openId); newUser.setArea(city); newUser.setSex(Integer.parseInt(gender)); newUser.setNickName(nickName); newUser.setCreateTime(new Date()); newUser.setStatus(0);//初始 if (!StringUtils.isBlank(unionid)) { newUser.setUnionid(unionid); } userService.instert(newUser); userId = newUser.getId(); } else { userId = user.getId(); } json.put("userId", userId); } if (!StringUtils.isBlank(session_key) && !StringUtils.isBlank(openId)) { //這段可不用redis存,直接返回session_key也可以 String userAgent = request.getHeader("user-agent"); String sessionid = tokenService.generateToken(userAgent, session_key); //將session_key存入redis redisUtil.setex(sessionid, session_key + "###" + userId, Constants.SESSION_KEY_EX); json.put("token", sessionid); responseBean = new ResponseBean(true, json); } else { responseBean = new ResponseBean<>(false, null, errmsg); } return responseBean; } catch (Exception e) { e.printStackTrace(); responseBean = new ResponseBean(false, UnicomResponseEnums.JSCODE2SESSION_ERRO); return responseBean; } }
解析:
這邊我的登錄獲取的session_key出于安全性考慮沒(méi)有直接在前端傳輸,而是存到了redis里面給到前端session_key的token傳輸,
而且session_key的銷(xiāo)毀時(shí)間是20分鐘,時(shí)間內(nèi)可以重復(fù)獲取用戶(hù)數(shù)據(jù).
如果只是簡(jiǎn)單使用或者對(duì)安全性要求不嚴(yán)的話(huà)可以直接傳session_key到前端保存.
session_key的作用:
校驗(yàn)用戶(hù)信息(wx.getUserInfo(OBJECT)返回的signature);
解密(wx.getUserInfo(OBJECT)返回的encryptedData);
按照官方的說(shuō)法,wx.checksession是用來(lái)檢查 wx.login(OBJECT) 的時(shí)效性,判斷登錄是否過(guò)期;
疑惑的是(openid,unionid )都是用戶(hù)唯一標(biāo)識(shí),不會(huì)因?yàn)閣x.login(OBJECT)的過(guò)期而改變,所以要是沒(méi)有使用wx.getUserInfo(OBJECT)獲得的用戶(hù)信息,確實(shí)沒(méi)必要使用wx.checksession()來(lái)檢查wx.login(OBJECT) 是否過(guò)期;
如果使用了wx.getUserInfo(OBJECT)獲得的用戶(hù)信息,還是有必要使用wx.checksession()來(lái)檢查wx.login(OBJECT) 是否過(guò)期的,因?yàn)橛脩?hù)有可能修改了頭像、昵稱(chēng)、城市,省份等信息,可以通過(guò)檢查wx.login(OBJECT) 是否過(guò)期來(lái)更新著些信息;
小程序的登錄狀態(tài)維護(hù)本質(zhì)就是維護(hù)session_key的時(shí)效性
這邊附上我的HttpUtils工具代碼,如果只要用get的話(huà)可以復(fù)制部分:
如果是直
/** * HttpUtils工具類(lèi) * * @author */ public class HttpUtils { /** * 請(qǐng)求方式:post */ public static String POST = "post"; /** * 編碼格式:utf-8 */ private static final String CHARSET_UTF_8 = "UTF-8"; /** * 報(bào)文頭部json */ private static final String APPLICATION_JSON = "application/json"; /** * 請(qǐng)求超時(shí)時(shí)間 */ private static final int CONNECT_TIMEOUT = 60 * 1000; /** * 傳輸超時(shí)時(shí)間 */ private static final int SO_TIMEOUT = 60 * 1000; /** * 日志 */ private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class); /** * @param protocol * @param url * @param paraMap * @return * @throws Exception */ public static String doPost(String protocol, String url, Map<String, Object> paraMap) throws Exception { CloseableHttpClient httpClient = null; CloseableHttpResponse resp = null; String rtnValue = null; try { if (protocol.equals("http")) { httpClient = HttpClients.createDefault(); } else { // 獲取https安全客戶(hù)端 httpClient = HttpUtils.getHttpsClient(); } HttpPost httpPost = new HttpPost(url); List<NameValuePair> list = msgs2valuePairs(paraMap); // List<NameValuePair> list = new ArrayList<NameValuePair>(); // if (null != paraMap &¶Map.size() > 0) { // for (Entry<String, Object> entry : paraMap.entrySet()) { // list.add(new BasicNameValuePair(entry.getKey(), entry // .getValue().toString())); // } // } RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(SO_TIMEOUT) .setConnectTimeout(CONNECT_TIMEOUT).build();// 設(shè)置請(qǐng)求和傳輸超時(shí)時(shí)間 httpPost.setConfig(requestConfig); httpPost.setEntity(new UrlEncodedFormEntity(list, CHARSET_UTF_8)); resp = httpClient.execute(httpPost); rtnValue = EntityUtils.toString(resp.getEntity(), CHARSET_UTF_8); } catch (Exception e) { logger.error(e.getMessage()); throw e; } finally { if (null != resp) { resp.close(); } if (null != httpClient) { httpClient.close(); } } return rtnValue; } /** * 獲取https,單向驗(yàn)證 * * @return * @throws Exception */ public static CloseableHttpClient getHttpsClient() throws Exception { try { TrustManager[] trustManagers = new TrustManager[]{new X509TrustManager() { public void checkClientTrusted( X509Certificate[] paramArrayOfX509Certificate, String paramString) throws CertificateException { } public void checkServerTrusted( X509Certificate[] paramArrayOfX509Certificate, String paramString) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }}; SSLContext sslContext = SSLContext .getInstance(SSLConnectionSocketFactory.TLS); sslContext.init(new KeyManager[0], trustManagers, new SecureRandom()); SSLContext.setDefault(sslContext); sslContext.init(null, trustManagers, null); SSLConnectionSocketFactory connectionSocketFactory = new SSLConnectionSocketFactory( sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); HttpClientBuilder clientBuilder = HttpClients.custom() .setSSLSocketFactory(connectionSocketFactory); clientBuilder.setRedirectStrategy(new LaxRedirectStrategy()); CloseableHttpClient httpClient = clientBuilder.build(); return httpClient; } catch (Exception e) { throw new Exception("http client 遠(yuǎn)程連接失敗", e); } } /** * post請(qǐng)求 * * @param msgs * @param url * @return * @throws ClientProtocolException * @throws UnknownHostException * @throws IOException */ public static String post(Map<String, Object> msgs, String url) throws ClientProtocolException, UnknownHostException, IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); try { HttpPost request = new HttpPost(url); List<NameValuePair> valuePairs = msgs2valuePairs(msgs); // List<NameValuePair> valuePairs = new ArrayList<NameValuePair>(); // if (null != msgs) { // for (Entry<String, Object> entry : msgs.entrySet()) { // if (entry.getValue() != null) { // valuePairs.add(new BasicNameValuePair(entry.getKey(), // entry.getValue().toString())); // } // } // } request.setEntity(new UrlEncodedFormEntity(valuePairs, CHARSET_UTF_8)); CloseableHttpResponse resp = httpClient.execute(request); return EntityUtils.toString(resp.getEntity(), CHARSET_UTF_8); } finally { httpClient.close(); } } /** * post請(qǐng)求 * * @param msgs * @param url * @return * @throws ClientProtocolException * @throws UnknownHostException * @throws IOException */ public static byte[] postGetByte(Map<String, Object> msgs, String url) throws ClientProtocolException, UnknownHostException, IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); InputStream inputStream = null; byte[] data = null; try { HttpPost request = new HttpPost(url); List<NameValuePair> valuePairs = msgs2valuePairs(msgs); // List<NameValuePair> valuePairs = new ArrayList<NameValuePair>(); // if (null != msgs) { // for (Entry<String, Object> entry : msgs.entrySet()) { // if (entry.getValue() != null) { // valuePairs.add(new BasicNameValuePair(entry.getKey(), // entry.getValue().toString())); // } // } // } request.setEntity(new UrlEncodedFormEntity(valuePairs, CHARSET_UTF_8)); CloseableHttpResponse response = httpClient.execute(request); try { // 獲取相應(yīng)實(shí)體 HttpEntity entity = response.getEntity(); if (entity != null) { inputStream = entity.getContent(); data = readInputStream(inputStream); } return data; } catch (Exception e) { e.printStackTrace(); } finally { httpClient.close(); return null; } } finally { httpClient.close(); } } /** 將流 保存為數(shù)據(jù)數(shù)組 * @param inStream * @return * @throws Exception */ public static byte[] readInputStream(InputStream inStream) throws Exception { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); // 創(chuàng)建一個(gè)Buffer字符串 byte[] buffer = new byte[1024]; // 每次讀取的字符串長(zhǎng)度,如果為-1,代表全部讀取完畢 int len = 0; // 使用一個(gè)輸入流從buffer里把數(shù)據(jù)讀取出來(lái) while ((len = inStream.read(buffer)) != -1) { // 用輸出流往buffer里寫(xiě)入數(shù)據(jù),中間參數(shù)代表從哪個(gè)位置開(kāi)始讀,len代表讀取的長(zhǎng)度 outStream.write(buffer, 0, len); } // 關(guān)閉輸入流 inStream.close(); // 把outStream里的數(shù)據(jù)寫(xiě)入內(nèi)存 return outStream.toByteArray(); } /** * get請(qǐng)求 * * @param msgs * @param url * @return * @throws ClientProtocolException * @throws UnknownHostException * @throws IOException */ public static String get(Map<String, Object> msgs, String url) throws ClientProtocolException, UnknownHostException, IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); try { List<NameValuePair> valuePairs = msgs2valuePairs(msgs); // List<NameValuePair> valuePairs = new ArrayList<NameValuePair>(); // if (null != msgs) { // for (Entry<String, Object> entry : msgs.entrySet()) { // if (entry.getValue() != null) { // valuePairs.add(new BasicNameValuePair(entry.getKey(), // entry.getValue().toString())); // } // } // } // EntityUtils.toString(new UrlEncodedFormEntity(valuePairs), // CHARSET); url = url + "?" + URLEncodedUtils.format(valuePairs, CHARSET_UTF_8); HttpGet request = new HttpGet(url); CloseableHttpResponse resp = httpClient.execute(request); return EntityUtils.toString(resp.getEntity(), CHARSET_UTF_8); } finally { httpClient.close(); } } public static <T> T post(Map<String, Object> msgs, String url, Class<T> clazz) throws ClientProtocolException, UnknownHostException, IOException { String json = HttpUtils.post(msgs, url); T t = JSON.parseObject(json, clazz); return t; } public static <T> T get(Map<String, Object> msgs, String url, Class<T> clazz) throws ClientProtocolException, UnknownHostException, IOException { String json = HttpUtils.get(msgs, url); T t = JSON.parseObject(json, clazz); return t; } public static String postWithJson(Map<String, Object> msgs, String url) throws ClientProtocolException, IOException { CloseableHttpClient httpClient = HttpClients.createDefault(); try { String jsonParam = JSON.toJSONString(msgs); HttpPost post = new HttpPost(url); post.setHeader("Content-Type", APPLICATION_JSON); post.setEntity(new StringEntity(jsonParam, CHARSET_UTF_8)); CloseableHttpResponse response = httpClient.execute(post); return new String(EntityUtils.toString(response.getEntity()).getBytes("iso8859-1"), CHARSET_UTF_8); } finally { httpClient.close(); } } public static <T> T postWithJson(Map<String, Object> msgs, String url, Class<T> clazz) throws ClientProtocolException, UnknownHostException, IOException { String json = HttpUtils.postWithJson(msgs, url); T t = JSON.parseObject(json, clazz); return t; } public static List<NameValuePair> msgs2valuePairs(Map<String, Object> msgs) { List<NameValuePair> valuePairs = new ArrayList<NameValuePair>(); if (null != msgs) { for (Entry<String, Object> entry : msgs.entrySet()) { if (entry.getValue() != null) { valuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString())); } } } return valuePairs; } }
接傳session_key到前端的,下面的可以不用看了.
如果是redis存的sesssion_key的token的話(huà),這邊附上登陸的時(shí)候的token轉(zhuǎn)換為session_key.
自定義攔截器注解:
import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AuthIgnore { }
攔截器部分代碼:
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { AuthIgnore annotation; if(handler instanceof HandlerMethod) { annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthIgnore.class); }else{ return true; } //如果有@AuthIgnore注解,則不驗(yàn)證token if(annotation != null){ return true; } // //獲取微信access_token; // if(!redisUtil.exists(Constants.ACCESS_TOKEN)){ // Map<String, Object> msgs = new HashMap<>(); // msgs.put("appid",appId); // msgs.put("secret",secret); // msgs.put("grant_type","client_credential"); // String data = HttpUtils.get(msgs,Constants.GETACCESSTOKEN); // java的網(wǎng)絡(luò)請(qǐng)求,返回字符串 // String errcode= JSONObject.parseObject(data).getString("errcode"); // String errmsg= JSONObject.parseObject(data).getString("errmsg"); // if(StringUtils.isBlank(errcode)){ // //存儲(chǔ)access_token // String access_token= JSONObject.parseObject(data).getString("access_token"); // long expires_in=Long.parseLong(JSONObject.parseObject(data).getString("expires_in")); // redisUtil.setex("ACCESS_TOKEN",access_token, expires_in); // }else{ // //異常返回?cái)?shù)據(jù)攔截,返回json數(shù)據(jù) // response.setCharacterEncoding("UTF-8"); // response.setContentType("application/json; charset=utf-8"); // PrintWriter out = response.getWriter(); // ResponseBean<Object> responseBean=new ResponseBean<>(false,null, errmsg); // out = response.getWriter(); // out.append(JSON.toJSON(responseBean).toString()); // return false; // } // } //獲取用戶(hù)憑證 String token = request.getHeader(Constants.USER_TOKEN); // if(StringUtils.isBlank(token)){ // token = request.getParameter(Constants.USER_TOKEN); // } // if(StringUtils.isBlank(token)){ // Object obj = request.getAttribute(Constants.USER_TOKEN); // if(null!=obj){ // token=obj.toString(); // } // } // //token憑證為空 // if(StringUtils.isBlank(token)){ // //token不存在攔截,返回json數(shù)據(jù) // response.setCharacterEncoding("UTF-8"); // response.setContentType("application/json; charset=utf-8"); // PrintWriter out = response.getWriter(); // try{ // ResponseBean<Object> responseBean=new ResponseBean<>(false,null, UnicomResponseEnums.TOKEN_EMPTY); // out = response.getWriter(); // out.append(JSON.toJSON(responseBean).toString()); // return false; // } // catch (Exception e) { // e.printStackTrace(); // response.sendError(500); // return false; // } // } if(token==null||!redisUtil.exists(token)){ //用戶(hù)未登錄,返回json數(shù)據(jù) response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter out = response.getWriter(); try{ ResponseBean<Object> responseBean=new ResponseBean<>(false,null, UnicomResponseEnums.DIS_LOGIN); out = response.getWriter(); out.append(JSON.toJSON(responseBean).toString()); return false; } catch (Exception e) { e.printStackTrace(); response.sendError(500); return false; } } tokenManager.refreshUserToken(token); return true; } }
過(guò)濾器配置:
@Configuration public class InterceptorConfig extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authorizationInterceptor()) .addPathPatterns("/**")// 攔截所有請(qǐng)求,通過(guò)判斷是否有 @AuthIgnore注解 決定是否需要登錄 .excludePathPatterns("/user/login");//排除登錄 } @Bean public AuthorizationInterceptor authorizationInterceptor() { return new AuthorizationInterceptor(); } }
token管理:
@Service public class TokenManager { @Resource private RedisUtil redisUtil; //生成token(格式為token:設(shè)備-加密的用戶(hù)名-時(shí)間-六位隨機(jī)數(shù)) public String generateToken(String userAgentStr, String username) { StringBuilder token = new StringBuilder("token:"); //設(shè)備 UserAgent userAgent = UserAgent.parseUserAgentString(userAgentStr); if (userAgent.getOperatingSystem().isMobileDevice()) { token.append("MOBILE-"); } else { token.append("PC-"); } //加密的用戶(hù)名 token.append(MD5Utils.MD5Encode(username) + "-"); //時(shí)間 token.append(new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + "-"); //六位隨機(jī)字符串 token.append(UUID.randomUUID().toString()); System.out.println("token-->" + token.toString()); return token.toString(); } /** * 登錄用戶(hù),創(chuàng)建token * * @param token */ //把token存到redis中 public void save(String token, Users user) { if (token.startsWith("token:PC")) { redisUtil.setex(token,JSON.toJSONString(user), Constants.TOKEN_EX); } else { redisUtil.setex(token,JSON.toJSONString(user), Constants.TOKEN_EX); } } /** * 刷新用戶(hù) * * @param token */ public void refreshUserToken(String token) { if (redisUtil.exists(token)) { String value=redisUtil.get(token); redisUtil.setex(token, value, Constants.TOKEN_EX); } } /** * 用戶(hù)退出登陸 * * @param token */ public void loginOut(String token) { redisUtil.remove(token); } /** * 獲取用戶(hù)信息 * * @param token * @return */ public Users getUserInfoByToken(String token) { if (redisUtil.exists(token)) { String jsonString = redisUtil.get(token); Users user =JSONObject.parseObject(jsonString, Users.class); return user; } return null; } }
redis工具類(lèi):
@Component public class RedisUtil { @Resource private RedisTemplate<String, String> redisTemplate; public void set(String key, String value) { ValueOperations<String, String> valueOperations = redisTemplate.opsForValue(); valueOperations.set(key, value); } public void setex(String key, String value, long seconds) { ValueOperations<String, String> valueOperations = redisTemplate.opsForValue(); valueOperations.set(key, value, seconds,TimeUnit.SECONDS); } public Boolean exists(String key) { return redisTemplate.hasKey(key); } public void remove(String key) { redisTemplate.delete(key); } public String get(String key) { ValueOperations<String, String> valueOperations = redisTemplate.opsForValue(); return valueOperations.get(key); } }
最后redis要實(shí)現(xiàn)序列化,序列化最終的目的是為了對(duì)象可以跨平臺(tái)存儲(chǔ),和進(jìn)行網(wǎng)絡(luò)傳輸。而我們進(jìn)行跨平臺(tái)存儲(chǔ)和網(wǎng)絡(luò)傳輸?shù)姆绞骄褪荌O,而我們的IO支持的數(shù)據(jù)格式就是字節(jié)數(shù)組。本質(zhì)上存儲(chǔ)和網(wǎng)絡(luò)傳輸 都需要經(jīng)過(guò) 把一個(gè)對(duì)象狀態(tài)保存成一種跨平臺(tái)識(shí)別的字節(jié)格式,然后其他的平臺(tái)才可以通過(guò)字節(jié)信息解析還原對(duì)象信息。
@Configuration @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) public class RedisConfig { @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate( RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); //使用fastjson序列化 FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class); // value值的序列化采用fastJsonRedisSerializer template.setValueSerializer(fastJsonRedisSerializer); template.setHashValueSerializer(fastJsonRedisSerializer); // key的序列化采用StringRedisSerializer template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean(StringRedisTemplate.class) public StringRedisTemplate stringRedisTemplate( RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
作者:gigass
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
總結(jié)
到此這篇關(guān)于基于redis的小程序登錄實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)redis小程序登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript數(shù)組、json對(duì)象、eval()函數(shù)用法實(shí)例分析
這篇文章主要介紹了JavaScript數(shù)組、json對(duì)象、eval()函數(shù)用法,結(jié)合實(shí)例形式分析了JS數(shù)組創(chuàng)建、賦值、連接、翻轉(zhuǎn),json對(duì)象定義、讀取,eval()函數(shù)的功能、使用等,需要的朋友可以參考下2019-02-02通過(guò)JS 獲取Mouse Position(鼠標(biāo)坐標(biāo))的代碼
最近我發(fā)現(xiàn)在webpage中獲取空間的絕對(duì)坐標(biāo)時(shí),如果有滾動(dòng)條就會(huì)有錯(cuò),后來(lái)用無(wú)名發(fā)現(xiàn)的方法得以解決。2009-09-09十個(gè)開(kāi)發(fā)人員面臨的最常見(jiàn)的JavaScript問(wèn)題總結(jié)
今天,JavaScript?是幾乎所有現(xiàn)代?Web?應(yīng)用的核心。這就是為什么JavaScript問(wèn)題,以及找到導(dǎo)致這些問(wèn)題的錯(cuò)誤,是?Web?發(fā)者的首要任務(wù)。本文總結(jié)了十個(gè)常見(jiàn)的問(wèn)題及解決方法,需要的可以參考一下2022-11-11layui動(dòng)態(tài)渲染生成select的option值方法
今天小編就為大家分享一篇layui動(dòng)態(tài)渲染生成select的option值方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09javascript實(shí)現(xiàn)淘寶幻燈片廣告展示效果
這篇文章主要介紹了javascript實(shí)現(xiàn)淘寶幻燈片廣告展示效果的方法,以實(shí)例形式完整講述了javascript實(shí)現(xiàn)幻燈效果的javascript、css及html實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-04-04js判斷一個(gè)元素是否為另一個(gè)元素的子元素的代碼
用js判斷一個(gè)元素是否為另一個(gè)元素的子元素,再做一些效果的時(shí)候經(jīng)常用到,特別是和鼠標(biāo)事件相關(guān)的應(yīng)用中,比如一個(gè)浮層,在鼠標(biāo)操作浮層內(nèi)元素的時(shí)候浮層顯示,當(dāng)點(diǎn)擊浮層外的元素的時(shí)候隱藏浮層2012-03-03JavaScript 小型打飛機(jī)游戲?qū)崿F(xiàn)原理說(shuō)明
這次為大家?guī)?lái)的小游戲是:打飛機(jī)。呃。。。我本人就寫(xiě)不出什么驚天大作的游戲的了,只能寫(xiě)寫(xiě)小游戲,代碼量小,又可以學(xué)習(xí),主要是想法思路,代碼量大,估計(jì)也沒(méi)啥人會(huì)去研究學(xué)習(xí)。。。2010-10-10